Menu
This commit is contained in:
parent
c6821b689d
commit
5ce1470b9e
|
@ -1,8 +1,8 @@
|
||||||
<div class="banner" *ngIf="instanceBannerUrl">
|
|
||||||
<img [src]="instanceBannerUrl" alt="Instance banner">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="margin-content mt-4">
|
<div class="margin-content mt-4">
|
||||||
|
<div class="banner mb-4" *ngIf="instanceBannerUrl">
|
||||||
|
<img class="rounded" [src]="instanceBannerUrl" alt="Instance banner">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="col-md-12 col-xl-6">
|
<div class="col-md-12 col-xl-6">
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<div class="form-group" formGroupName="trending">
|
<div class="form-group" formGroupName="trending">
|
||||||
<ng-container formGroupName="videos">
|
<ng-container formGroupName="videos">
|
||||||
<ng-container formGroupName="algorithms">
|
<ng-container formGroupName="algorithms">
|
||||||
<label i18n for="trendingVideosAlgorithmsDefault">Default trending page</label>
|
<label i18n for="trendingVideosAlgorithmsDefault">Default trending algorithm</label>
|
||||||
|
|
||||||
<div class="peertube-select-container">
|
<div class="peertube-select-container">
|
||||||
<select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control">
|
<select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control">
|
||||||
|
|
|
@ -164,9 +164,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
links = links.concat([
|
links = links.concat([
|
||||||
{ label: $localize`Discover`, path: '/videos/overview' },
|
{ label: $localize`Discover`, path: '/videos/overview' },
|
||||||
{ label: $localize`Trending`, path: '/videos/trending' },
|
{ label: $localize`Browse videos`, path: '/videos/browse' }
|
||||||
{ label: $localize`Recently added`, path: '/videos/recently-added' },
|
|
||||||
{ label: $localize`Local videos`, path: '/videos/local' }
|
|
||||||
])
|
])
|
||||||
|
|
||||||
this.defaultLandingPageOptions = links.map(o => ({
|
this.defaultLandingPageOptions = links.map(o => ({
|
||||||
|
|
|
@ -39,7 +39,7 @@ import { VideoRedundancyInformationComponent } from './video-redundancy-informat
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class VideoRedundanciesListComponent extends RestTable implements OnInit {
|
export class VideoRedundanciesListComponent extends RestTable implements OnInit {
|
||||||
private static LOCAL_STORAGE_DISPLAY_TYPE = 'video-redundancies-list-display-type'
|
private static LS_DISPLAY_TYPE = 'video-redundancies-list-display-type'
|
||||||
|
|
||||||
videoRedundancies: VideoRedundancy[] = []
|
videoRedundancies: VideoRedundancy[] = []
|
||||||
totalRecords = 0
|
totalRecords = 0
|
||||||
|
@ -211,12 +211,12 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadSelectLocalStorage () {
|
private loadSelectLocalStorage () {
|
||||||
const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE)
|
const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LS_DISPLAY_TYPE)
|
||||||
if (displayType) this.displayType = displayType as VideoRedundanciesTarget
|
if (displayType) this.displayType = displayType as VideoRedundanciesTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveSelectLocalStorage () {
|
private saveSelectLocalStorage () {
|
||||||
peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE, this.displayType)
|
peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LS_DISPLAY_TYPE, this.displayType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private bytesToHuman (bytes: number) {
|
private bytesToHuman (bytes: number) {
|
||||||
|
|
|
@ -71,7 +71,7 @@ type UserForList = User & {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class UserListComponent extends RestTable <User> implements OnInit {
|
export class UserListComponent extends RestTable <User> implements OnInit {
|
||||||
private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
|
private static readonly LS_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns'
|
||||||
|
|
||||||
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSelectedColumns () {
|
loadSelectedColumns () {
|
||||||
const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY)
|
const result = this.peertubeLocalStorage.getItem(UserListComponent.LS_SELECTED_COLUMNS_KEY)
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
try {
|
try {
|
||||||
|
@ -201,7 +201,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSelectedColumns () {
|
saveSelectedColumns () {
|
||||||
this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
|
this.peertubeLocalStorage.setItem(UserListComponent.LS_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns))
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdentifier () {
|
getIdentifier () {
|
||||||
|
|
|
@ -38,8 +38,8 @@ import { JobService } from './job.service'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class JobsComponent extends RestTable implements OnInit {
|
export class JobsComponent extends RestTable implements OnInit {
|
||||||
private static LOCAL_STORAGE_STATE = 'jobs-list-state'
|
private static LS_STATE = 'jobs-list-state'
|
||||||
private static LOCAL_STORAGE_TYPE = 'jobs-list-type'
|
private static LS_TYPE = 'jobs-list-type'
|
||||||
|
|
||||||
jobState?: JobStateClient
|
jobState?: JobStateClient
|
||||||
jobStates: JobStateClient[] = [ 'all', 'active', 'completed', 'failed', 'waiting', 'delayed' ]
|
jobStates: JobStateClient[] = [ 'all', 'active', 'completed', 'failed', 'waiting', 'delayed' ]
|
||||||
|
@ -175,15 +175,15 @@ export class JobsComponent extends RestTable implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadJobStateAndType () {
|
private loadJobStateAndType () {
|
||||||
const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE)
|
const state = peertubeLocalStorage.getItem(JobsComponent.LS_STATE)
|
||||||
if (state) this.jobState = state as JobState
|
if (state) this.jobState = state as JobState
|
||||||
|
|
||||||
const jobType = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE)
|
const jobType = peertubeLocalStorage.getItem(JobsComponent.LS_TYPE)
|
||||||
if (jobType) this.jobType = jobType as JobType
|
if (jobType) this.jobType = jobType as JobType
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveJobStateAndType () {
|
private saveJobStateAndType () {
|
||||||
peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_STATE, this.jobState)
|
peertubeLocalStorage.setItem(JobsComponent.LS_STATE, this.jobState)
|
||||||
peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_TYPE, this.jobType)
|
peertubeLocalStorage.setItem(JobsComponent.LS_TYPE, this.jobType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { LogsService } from './logs.service'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class LogsComponent implements OnInit {
|
export class LogsComponent implements OnInit {
|
||||||
private static LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
private static LS_LOG_TYPE_CHOICE_KEY = 'admin-logs-log-type-choice'
|
||||||
|
|
||||||
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
|
@ViewChild('logsElement', { static: true }) logsElement: ElementRef<HTMLElement>
|
||||||
@ViewChild('logsContent', { static: true }) logsContent: ElementRef<HTMLElement>
|
@ViewChild('logsContent', { static: true }) logsContent: ElementRef<HTMLElement>
|
||||||
|
@ -69,7 +69,7 @@ export class LogsComponent implements OnInit {
|
||||||
refresh () {
|
refresh () {
|
||||||
this.logs = []
|
this.logs = []
|
||||||
|
|
||||||
this.localStorage.setItem(LogsComponent.LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY, this.logType)
|
this.localStorage.setItem(LogsComponent.LS_LOG_TYPE_CHOICE_KEY, this.logType)
|
||||||
|
|
||||||
this.load()
|
this.load()
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ export class LogsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadPreviousChoices () {
|
private loadPreviousChoices () {
|
||||||
this.logType = this.localStorage.getItem(LogsComponent.LOCAL_STORAGE_LOG_TYPE_CHOICE_KEY)
|
this.logType = this.localStorage.getItem(LogsComponent.LS_LOG_TYPE_CHOICE_KEY)
|
||||||
|
|
||||||
if (this.logType !== 'standard' && this.logType !== 'audit') this.logType = 'audit'
|
if (this.logType !== 'standard' && this.logType !== 'audit') this.logType = 'audit'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Routes } from '@angular/router'
|
||||||
|
import { VideoChannelCreateComponent } from '@app/shared/standalone-channels/video-channel-create.component'
|
||||||
|
import { VideoChannelUpdateComponent } from '@app/shared/standalone-channels/video-channel-update.component'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
component: VideoChannelCreateComponent,
|
||||||
|
data: {
|
||||||
|
meta: {
|
||||||
|
title: $localize`Create a new video channel`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'update/:videoChannelName',
|
||||||
|
component: VideoChannelUpdateComponent,
|
||||||
|
data: {
|
||||||
|
meta: {
|
||||||
|
title: $localize`Update video channel`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] satisfies Routes
|
|
@ -39,18 +39,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $mobile-view) and (max-width: #{$small-view + $menu-width}) {
|
|
||||||
:host-context(.main-col:not(.expanded)) {
|
|
||||||
.header {
|
|
||||||
a {
|
|
||||||
font-size: 0;
|
|
||||||
padding: 0 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peertube-select-container {
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-channel-buttons">
|
<div class="video-channel-buttons">
|
||||||
<my-edit-button label [ptRouterLink]="[ '/manage/update', videoChannel.nameWithHost ]"></my-edit-button>
|
<my-edit-button label [ptRouterLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]"></my-edit-button>
|
||||||
<my-delete-button label (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
|
<my-delete-button label (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -123,9 +123,7 @@ my-edit-button {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $small-view) {
|
@media screen and (min-width: $small-view) {
|
||||||
:host-context(.expanded) {
|
.video-channel-buttons {
|
||||||
.video-channel-buttons {
|
float: right;
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<div class="root" *ngIf="videoChannel">
|
<div class="root" *ngIf="videoChannel">
|
||||||
<div class="margin-content banner" *ngIf="videoChannel.bannerUrl">
|
<div class="margin-content">
|
||||||
<img [src]="videoChannel.bannerUrl" alt="Channel banner">
|
<div class="banner" *ngIf="videoChannel.bannerUrl">
|
||||||
|
<img [src]="videoChannel.bannerUrl" alt="Channel banner">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="margin-content channel-info">
|
<div class="margin-content channel-info">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@use '_button-mixins' as *;
|
@use '_button-mixins' as *;
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
--co-global-top-padding: 60px;
|
--co-global-top-padding: 2rem;
|
||||||
--co-channel-img-margin: 30px;
|
--co-channel-img-margin: 30px;
|
||||||
--co-font-size: 16px;
|
--co-font-size: 16px;
|
||||||
--co-channel-handle-font-size: 16px;
|
--co-channel-handle-font-size: 16px;
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
.actor-info {
|
.actor-info {
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
> h4,
|
> h4,
|
||||||
> .actor-handle {
|
> .actor-handle {
|
||||||
|
@ -89,6 +90,7 @@
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
font-size: var(--co-font-size);
|
font-size: var(--co-font-size);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
.avatar-row {
|
.avatar-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -222,7 +224,7 @@ my-copy-button {
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
@media screen and (max-width: $mobile-view) {
|
||||||
.root {
|
.root {
|
||||||
--co-global-top-padding: 15px;
|
--co-global-top-padding: 1rem;
|
||||||
--co-font-size: 14px;
|
--co-font-size: 14px;
|
||||||
--co-channel-handle-font-size: 13px;
|
--co-channel-handle-font-size: 13px;
|
||||||
--co-owner-handle-font-size: 13px;
|
--co-owner-handle-font-size: 13px;
|
||||||
|
|
|
@ -89,9 +89,3 @@ $nav-link-height: 40px;
|
||||||
@media screen and (max-width: $small-view) {
|
@media screen and (max-width: $small-view) {
|
||||||
@include nav-scroll();
|
@include nav-scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: #{$small-view + $menu-width}) {
|
|
||||||
:host-context(.main-col:not(.expanded)) {
|
|
||||||
@include nav-scroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
@media screen and (max-width: $small-view) {
|
||||||
.action-button .icon-text {
|
.action-button .icon-text {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ form {
|
||||||
min-height: calc(#{$peertube-textarea-height} - 15px * 2);
|
min-height: calc(#{$peertube-textarea-height} - 15px * 2);
|
||||||
|
|
||||||
@include peertube-textarea(100%, $peertube-textarea-height);
|
@include peertube-textarea(100%, $peertube-textarea-height);
|
||||||
@include button-focus(pvar(--primary-100));
|
|
||||||
@include padding-right($markdown-icon-width + 15px !important);
|
@include padding-right($markdown-icon-width + 15px !important);
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube#privacy">More information</a>
|
<a class="link-orange" i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about/peertube#privacy">More information</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button i18n class="ms-2 peertube-button primary-button" (click)="acceptedPrivacyConcern()">
|
<button i18n class="ms-2 peertube-button primary-button" (click)="acceptedPrivacyConcern()">
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
||||||
width: calc(100% - #{$menu-width});
|
width: calc(100% - #{$menu-width} - (#{pvar(--x-margin-content)} * 2));
|
||||||
z-index: z(privacymsg);
|
z-index: z(privacymsg);
|
||||||
|
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
@ -16,27 +17,8 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: rgba(0, 0, 0, 0.9);
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
// If the view is expanded
|
@include margin-left(pvar(--x-margin-content));
|
||||||
:host-context(.expanded) {
|
|
||||||
.privacy-concerns {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid higher z-index when overlay on touchscreens
|
|
||||||
:host-context(.menu-open) {
|
|
||||||
.privacy-concerns {
|
|
||||||
z-index: z(overlay) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or if we are in the small view
|
|
||||||
@media screen and (max-width: $small-view) {
|
|
||||||
.privacy-concerns {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy-concerns-text {
|
.privacy-concerns-text {
|
||||||
|
@ -44,23 +26,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: pvar(--primary);
|
color: #fff;
|
||||||
transition: color 0.3s;
|
|
||||||
|
|
||||||
@include disable-default-a-behaviour;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: pvar(--bg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1300px) {
|
:host-context(.main-col.expanded) {
|
||||||
.privacy-concerns {
|
.privacy-concerns {
|
||||||
font-size: 12px;
|
width: calc(100% - #{$menu-collapsed-width} - (#{pvar(--x-margin-content)} * 2));
|
||||||
padding: 2px 5px;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.privacy-concerns-text {
|
// Or if we are in the small view
|
||||||
margin: 0;
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
.privacy-concerns {
|
||||||
|
width: 100% !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
|
||||||
|
@include margin-left(0 !important);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { NgIf } from '@angular/common'
|
||||||
imports: [ NgIf ]
|
imports: [ NgIf ]
|
||||||
})
|
})
|
||||||
export class PrivacyConcernsComponent implements OnInit {
|
export class PrivacyConcernsComponent implements OnInit {
|
||||||
private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
|
private static LS_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
|
||||||
|
|
||||||
@Input() video: Video
|
@Input() video: Video
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export class PrivacyConcernsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptedPrivacyConcern () {
|
acceptedPrivacyConcern () {
|
||||||
peertubeLocalStorage.setItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY, 'true')
|
peertubeLocalStorage.setItem(PrivacyConcernsComponent.LS_PRIVACY_CONCERN_KEY, 'true')
|
||||||
|
|
||||||
this.display = false
|
this.display = false
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,6 @@ export class PrivacyConcernsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private alreadyAccepted () {
|
private alreadyAccepted () {
|
||||||
return peertubeLocalStorage.getItem(PrivacyConcernsComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true'
|
return peertubeLocalStorage.getItem(PrivacyConcernsComponent.LS_PRIVACY_CONCERN_KEY) === 'true'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,40 @@
|
||||||
<div class="root" [ngClass]="{ 'theater-enabled': theaterEnabled }" [hidden]="!playlist && !video">
|
<div class="root" [ngClass]="{ 'theater-enabled': theaterEnabled }" [hidden]="!playlist && !video">
|
||||||
<!-- We need the video container for videojs so we just hide it -->
|
<div class="margin-content player-margin-content">
|
||||||
<div id="video-wrapper">
|
<!-- We need the video container for videojs so we just hide it -->
|
||||||
<div *ngIf="remoteServerDown" class="remote-server-down">
|
<div id="video-wrapper">
|
||||||
<ng-container i18n>Sorry, but this video did not load because the remote instance did not respond.</ng-container>
|
<div *ngIf="remoteServerDown" class="remote-server-down">
|
||||||
|
<ng-container i18n>Sorry, but this video did not load because the remote instance did not respond.</ng-container>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<ng-container i18n>Please try refreshing the page, or try again later.</ng-container>
|
<ng-container i18n>Please try refreshing the page, or try again later.</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="videojs-wrapper">
|
||||||
|
<video #playerElement class="video-js vjs-peertube-skin" playsinline="true"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="player-widget-component">
|
||||||
|
<my-video-watch-playlist
|
||||||
|
#videoWatchPlaylist [playlist]="playlist"
|
||||||
|
[hidden]="transcriptionWidgetOpened"
|
||||||
|
(noVideoFound)="onPlaylistNoVideoFound()" (videoFound)="onPlaylistVideoFound($event)"
|
||||||
|
></my-video-watch-playlist>
|
||||||
|
|
||||||
|
@if (transcriptionWidgetOpened) {
|
||||||
|
<my-video-transcription
|
||||||
|
[video]="video" [captions]="videoCaptions" [currentTime]="getCurrentTime()"
|
||||||
|
(segmentClicked)="handleTimestampClicked($event)" (closeTranscription)="transcriptionWidgetOpened = false"
|
||||||
|
></my-video-transcription>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<my-plugin-placeholder pluginId="player-next"></my-plugin-placeholder>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="videojs-wrapper">
|
<my-video-alert [video]="video" [user]="user" [noPlaylistVideoFound]="noPlaylistVideoFound"></my-video-alert>
|
||||||
<video #playerElement class="video-js vjs-peertube-skin" playsinline="true"></video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="player-widget-component">
|
|
||||||
<my-video-watch-playlist
|
|
||||||
#videoWatchPlaylist [playlist]="playlist"
|
|
||||||
[hidden]="transcriptionWidgetOpened"
|
|
||||||
(noVideoFound)="onPlaylistNoVideoFound()" (videoFound)="onPlaylistVideoFound($event)"
|
|
||||||
></my-video-watch-playlist>
|
|
||||||
|
|
||||||
@if (transcriptionWidgetOpened) {
|
|
||||||
<my-video-transcription
|
|
||||||
[video]="video" [captions]="videoCaptions" [currentTime]="getCurrentTime()"
|
|
||||||
(segmentClicked)="handleTimestampClicked($event)" (closeTranscription)="transcriptionWidgetOpened = false"
|
|
||||||
></my-video-transcription>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<my-plugin-placeholder pluginId="player-next"></my-plugin-placeholder>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<my-video-alert [video]="video" [user]="user" [noPlaylistVideoFound]="noPlaylistVideoFound"></my-video-alert>
|
|
||||||
|
|
||||||
<!-- Video information -->
|
<!-- Video information -->
|
||||||
<div *ngIf="video" class="margin-content video-bottom">
|
<div *ngIf="video" class="margin-content video-bottom">
|
||||||
<div class="video-info">
|
<div class="video-info">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@use '_miniature' as *;
|
@use '_miniature' as *;
|
||||||
|
|
||||||
$video-default-height: 66vh;
|
$video-default-height: 66vh;
|
||||||
$video-max-height: calc(100vh - #{$header-height} - #{$theater-bottom-space});
|
$video-max-height: calc(100vh - #{pvar(--header-height)} - #{$theater-bottom-space});
|
||||||
|
|
||||||
@mixin player-widget-below-player {
|
@mixin player-widget-below-player {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
@ -53,6 +53,7 @@ $video-max-height: calc(100vh - #{$header-height} - #{$theater-bottom-space});
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border-radius:5px;
|
||||||
|
|
||||||
#videojs-wrapper {
|
#videojs-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -256,7 +257,7 @@ my-video-comments {
|
||||||
--co-player-portrait-mode: 1;
|
--co-player-portrait-mode: 1;
|
||||||
--co-player-height: calc(100vw / var(--co-player-ratio)) !important;
|
--co-player-height: calc(100vw / var(--co-player-ratio)) !important;
|
||||||
|
|
||||||
max-height: calc(100vh - #{$header-height} - #{$player-portrait-bottom-space});
|
max-height: calc(100vh - #{pvar(--header-height)} - #{$player-portrait-bottom-space});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +276,11 @@ my-video-comments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
.margin-content.player-margin-content {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.video-info-name {
|
.video-info-name {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,43 @@
|
||||||
<h1 class="visually-hidden" i18n>Discover</h1>
|
<h1 class="visually-hidden" i18n>Discover</h1>
|
||||||
<div class="margin-content">
|
|
||||||
|
|
||||||
@if (notResults) {
|
@if (notResults) {
|
||||||
<div class="no-results" i18n>No results.</div>
|
<div class="margin-content no-results" i18n>No results.</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="quick-access mt-3">
|
<div class="margin-content quick-access mt-3">
|
||||||
<div #quickAccessContent class="quick-access-links" [ngClass]="{ 'see-all-quick-links': seeAllQuickLinks }">
|
<div #quickAccessContent class="quick-access-links" [ngClass]="{ 'see-all-quick-links': seeAllQuickLinks }">
|
||||||
<span class="me-2 fg-100">Quick access:</span>
|
<span class="me-2 fg-100">Quick access:</span>
|
||||||
|
|
||||||
@for (quickAccess of quickAccessLinks; track quickAccess.label) {
|
@for (quickAccess of quickAccessLinks; track quickAccess.label) {
|
||||||
<a class="me-2" [routerLink]="quickAccess.routerLink" [queryParams]="quickAccess.queryParams">{{ quickAccess.label }}</a>
|
<a class="me-2" [routerLink]="quickAccess.routerLink" [queryParams]="quickAccess.queryParams">{{ quickAccess.label }}</a>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
<button *ngIf="!seeAllQuickLinks && quickAccessOverflow" type="button" class="peertube-button tertiary-button" (click)="seeAllQuickLinks = true" i18n>More</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<button *ngIf="!seeAllQuickLinks && quickAccessOverflow" type="button" class="peertube-button tertiary-button" (click)="seeAllQuickLinks = true" i18n>More</button>
|
||||||
myInfiniteScroller (nearOfBottom)="onNearOfBottom()"
|
</div>
|
||||||
[dataObservable]="onDataSubject.asObservable()" setAngularState="true" [parentDisabled]="disabled"
|
|
||||||
>
|
|
||||||
<div class="section videos" *ngFor="let object of objects">
|
|
||||||
|
|
||||||
<div class="section-header d-flex justify-content-between align-items-start">
|
<div
|
||||||
<h1 class="section-title">
|
class="margin-content videos-margin-content"
|
||||||
<my-actor-avatar *ngIf="object.channel" size="40px" actorType="channel" [actor]="object.channel"></my-actor-avatar>
|
myInfiniteScroller (nearOfBottom)="onNearOfBottom()"
|
||||||
|
[dataObservable]="onDataSubject.asObservable()" setAngularState="true" [parentDisabled]="disabled"
|
||||||
|
>
|
||||||
|
<div class="section videos" *ngFor="let object of objects">
|
||||||
|
|
||||||
<a class="text-fg border-highlight text-decoration-none" routerLink="/search" [queryParams]="object.queryParams">{{ object.label }}</a>
|
<div class="section-header d-flex flex-wrap justify-content-between align-items-start mb-3">
|
||||||
|
<h1 class="section-title">
|
||||||
|
<my-actor-avatar *ngIf="object.channel" size="40px" actorType="channel" [actor]="object.channel"></my-actor-avatar>
|
||||||
|
|
||||||
<span class="fg-100 fs-7 mx-2">·</span>
|
<a class="text-fg border-highlight text-decoration-none" routerLink="/search" [queryParams]="object.queryParams">{{ object.label }}</a>
|
||||||
<span i18n class="fg-100 fs-7">{{ object.type }}</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<my-button theme="primary" [routerLink]="object.routerLink" [queryParams]="object.queryParams">{{ object.buttonLabel }}</my-button>
|
<span class="fg-100 fs-7 mx-2">·</span>
|
||||||
</div>
|
<span i18n class="fg-100 fs-7">{{ object.type }}</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div class="video-wrapper" *ngFor="let video of object.videos">
|
<my-button class="ms-2 d-none-mw" theme="primary" [routerLink]="object.routerLink" [queryParams]="object.queryParams">{{ object.buttonLabel }}</my-button>
|
||||||
<my-video-miniature [video]="video" [user]="userMiniature" [displayVideoActions]="true"></my-video-miniature>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="video-wrapper" *ngFor="let video of object.videos">
|
||||||
|
<my-video-miniature [video]="video" [user]="userMiniature" [displayVideoActions]="true"></my-video-miniature>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
</div>
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
@include margin-bottom(2rem);
|
@include margin-bottom(2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-content {
|
.margin-content.videos-margin-content {
|
||||||
@include grid-videos-miniature-layout-with-margins($rows-limit: 2);
|
@include grid-videos-miniature-layout-with-margins($rows-limit: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
overflow: initial;
|
overflow: initial;
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
@include margin-left(10px);
|
@include margin-left(pvar(--x-margin-content));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="sub-header-container">
|
<div class="sub-header-container">
|
||||||
<my-menu id="left-menu" role="navigation" aria-label="Main menu" i18n-ariaLabel></my-menu>
|
<my-menu id="left-menu" role="navigation" aria-label="Main menu" i18n-ariaLabel></my-menu>
|
||||||
|
|
||||||
<main #mainContent tabindex="-1" id="content" class="main-col" [ngClass]="{ expanded: menu.isMenuCollapsed() }">
|
<main #mainContent tabindex="-1" id="content" class="main-col" [ngClass]="{ expanded: menu.isCollapsed() }">
|
||||||
|
|
||||||
<div class="main-row">
|
<div class="main-row">
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-row {
|
.main-row {
|
||||||
min-height: calc(100vh - #{$header-height} - #{$footer-height} - #{$footer-margin});
|
min-height: calc(100vh - #{pvar(--header-height)} - #{$footer-height} - #{$footer-margin});
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-header-container {
|
.sub-header-container {
|
||||||
margin-top: $header-height;
|
margin-top: pvar(--header-height);
|
||||||
background-color: pvar(--bg);
|
background-color: pvar(--bg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root-header {
|
.root-header {
|
||||||
height: $header-height;
|
height: pvar(--header-height);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -56,9 +56,9 @@ const routes: Routes = [
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'manage/update',
|
path: 'manage/update/:channel',
|
||||||
pathMatch: 'prefix',
|
pathMatch: 'full',
|
||||||
redirectTo: '/my-library/video-channels/update'
|
redirectTo: '/my-library/video-channels/update/:channel'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class AuthService {
|
||||||
private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token'
|
private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token'
|
||||||
private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token'
|
private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token'
|
||||||
private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me'
|
private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me'
|
||||||
private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
|
private static LS_OAUTH_CLIENT_KEYS = {
|
||||||
CLIENT_ID: 'client_id',
|
CLIENT_ID: 'client_id',
|
||||||
CLIENT_SECRET: 'client_secret'
|
CLIENT_SECRET: 'client_secret'
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,8 @@ export class AuthService {
|
||||||
tokensRefreshed = new ReplaySubject<void>(1)
|
tokensRefreshed = new ReplaySubject<void>(1)
|
||||||
loggedInHotkeys: Hotkey[]
|
loggedInHotkeys: Hotkey[]
|
||||||
|
|
||||||
private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
|
private clientId: string = peertubeLocalStorage.getItem(AuthService.LS_OAUTH_CLIENT_KEYS.CLIENT_ID)
|
||||||
private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
|
private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LS_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
|
||||||
private loginChanged: Subject<AuthStatus>
|
private loginChanged: Subject<AuthStatus>
|
||||||
private user: AuthUser = null
|
private user: AuthUser = null
|
||||||
private refreshingTokenObservable: Observable<void>
|
private refreshingTokenObservable: Observable<void>
|
||||||
|
@ -89,8 +89,8 @@ export class AuthService {
|
||||||
this.clientId = res.client_id
|
this.clientId = res.client_id
|
||||||
this.clientSecret = res.client_secret
|
this.clientSecret = res.client_secret
|
||||||
|
|
||||||
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId)
|
peertubeLocalStorage.setItem(AuthService.LS_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId)
|
||||||
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
|
peertubeLocalStorage.setItem(AuthService.LS_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
|
||||||
|
|
||||||
logger.info('Client credentials loaded.')
|
logger.info('Client credentials loaded.')
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,16 +17,12 @@ export class MenuService {
|
||||||
// Do not display menu on small or touch screens
|
// Do not display menu on small or touch screens
|
||||||
if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) {
|
if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) {
|
||||||
this.setMenuCollapsed(true)
|
this.setMenuCollapsed(true)
|
||||||
|
} else {
|
||||||
|
this.setMenuCollapsed(this.localStorageService.getItem(MenuService.LS_MENU_COLLAPSED) === 'true')
|
||||||
|
this.menuChangedByUser = this.menuCollapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleWindowResize()
|
this.handleWindowResize()
|
||||||
|
|
||||||
this.menuCollapsed = this.localStorageService.getItem(MenuService.LS_MENU_COLLAPSED) === 'true'
|
|
||||||
this.menuChangedByUser = this.menuCollapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
isMenuCollapsed () {
|
|
||||||
return this.menuCollapsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMenu () {
|
toggleMenu () {
|
||||||
|
@ -43,21 +39,16 @@ export class MenuService {
|
||||||
setMenuCollapsed (collapsed: boolean) {
|
setMenuCollapsed (collapsed: boolean) {
|
||||||
this.menuCollapsed = collapsed
|
this.menuCollapsed = collapsed
|
||||||
|
|
||||||
if (!this.screenService.isInTouchScreen()) return
|
if (this.menuCollapsed) {
|
||||||
|
document.body.classList.remove('menu-open')
|
||||||
// On touch screens, lock body scroll and display content overlay when memu is opened
|
} else {
|
||||||
if (!this.menuCollapsed) {
|
|
||||||
document.body.classList.add('menu-open')
|
document.body.classList.add('menu-open')
|
||||||
this.screenService.onFingerSwipe('left', () => this.setMenuCollapsed(true))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.classList.remove('menu-open')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onResize () {
|
onResize () {
|
||||||
if (this.screenService.isInSmallView() && !this.menuChangedByUser) {
|
if (this.screenService.isInSmallView() && !this.menuChangedByUser) {
|
||||||
this.menuCollapsed = true
|
this.setMenuCollapsed(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,35 @@
|
||||||
<div class="root py-4 px-4 w-100 d-flex justify-content-between">
|
<div class="root" [hidden]="!loaded || (loggedIn && !user.account)">
|
||||||
<a class="peertube-title me-3" [routerLink]="getDefaultRoute()" [queryParams]="getDefaultRouteQuery()">
|
<a class="peertube-title" [routerLink]="getDefaultRoute()" [queryParams]="getDefaultRouteQuery()">
|
||||||
|
|
||||||
<span class="icon-logo"></span>
|
<span class="icon-logo"></span>
|
||||||
<span class="instance-name">{{ instanceName }}</span>
|
<span class="instance-name">{{ instanceName }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="d-flex align-items-center" [hidden]="!loaded || (loggedIn && !user.account)">
|
<my-search-typeahead></my-search-typeahead>
|
||||||
<my-search-typeahead class="w-100 me-5"></my-search-typeahead>
|
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
@if (!loggedIn) {
|
@if (!loggedIn) {
|
||||||
<my-button theme="tertiary" rounded="true" class="me-3" icon="cog" (click)="openQuickSettings()"></my-button>
|
<my-button theme="tertiary" rounded="true" class="margin-button" icon="cog" (click)="openQuickSettings()"></my-button>
|
||||||
|
|
||||||
<a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link secondary-button w-100 text-truncate me-3">
|
<a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link secondary-button w-100 ellipsis margin-button">
|
||||||
<my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
|
<my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<my-login-link className="peertube-button-link primary-button w-100 text-truncate"></my-login-link>
|
<my-login-link class="login-label" className="peertube-button-link primary-button w-100 ellipsis"></my-login-link>
|
||||||
|
<my-login-link class="login-icon" className="peertube-button-link primary-button icon-only w-100 ellipsis" icon="true" label=""></my-login-link>
|
||||||
} @else {
|
} @else {
|
||||||
<my-notification-dropdown class="me-3" (navigate)="onActiveLinkScrollToAnchor($event)"></my-notification-dropdown>
|
<my-notification-dropdown class="margin-button"></my-notification-dropdown>
|
||||||
|
|
||||||
<my-button theme="tertiary" rounded="true" class="me-3" icon="cog" ptRouterLink="/my-account"></my-button>
|
<my-button theme="tertiary" rounded="true" class="margin-button" icon="cog" ptRouterLink="/my-account"></my-button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="logged-in-container" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left auto"
|
class="logged-in-container" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left auto"
|
||||||
container="body" (openChange)="onDropdownOpenChange($event)"
|
container="body"
|
||||||
>
|
>
|
||||||
<button class="tertiary-button" ngbDropdownToggle>
|
<button class="tertiary-button" ngbDropdownToggle>
|
||||||
<my-actor-avatar [actor]="user.account" actorType="account" size="34" class="me-2"></my-actor-avatar>
|
<my-actor-avatar [actor]="user.account" actorType="account" size="34" responseSize="true"></my-actor-avatar>
|
||||||
|
|
||||||
<div class="logged-in-info text-start">
|
<div class="logged-in-info text-start ms-2">
|
||||||
<div class="display-name ellipsis">{{ user.account?.displayName }}</div>
|
<div class="display-name ellipsis">{{ user.account?.displayName }}</div>
|
||||||
|
|
||||||
<div class="username ellipsis fs-8">@{{ user.username }}</div>
|
<div class="username ellipsis fs-8">@{{ user.username }}</div>
|
||||||
|
@ -37,8 +38,8 @@
|
||||||
|
|
||||||
<div ngbDropdownMenu>
|
<div ngbDropdownMenu>
|
||||||
<a
|
<a
|
||||||
*ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/a', user.account.nameWithHost ]"
|
*ngIf="user.account" ngbDropdownItem class="dropdown-item" [routerLink]="[ '/a', user.account.nameWithHost ]"
|
||||||
#profile (click)="onActiveLinkScrollToAnchor(profile)"
|
#profile
|
||||||
>
|
>
|
||||||
<my-global-icon iconName="user" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container>
|
<my-global-icon iconName="user" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
@ -46,8 +47,8 @@
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
*ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account"
|
*ngIf="user.account" ngbDropdownItem class="dropdown-item" routerLink="/my-account"
|
||||||
#manageAccount (click)="onActiveLinkScrollToAnchor(manageAccount)"
|
#manageAccount
|
||||||
>
|
>
|
||||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> <ng-container i18n>Manage my account</ng-container>
|
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> <ng-container i18n>Manage my account</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
@ -76,6 +77,8 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<my-button theme="tertiary" rounded="true" class="menu-button margin-button" icon="menu" (click)="toggleMenu()"></my-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<my-language-chooser #languageChooserModal></my-language-chooser>
|
<my-language-chooser #languageChooserModal></my-language-chooser>
|
||||||
|
|
|
@ -1,48 +1,72 @@
|
||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
@use '_button-mixins' as *;
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
|
--co-logo-size: 34px;
|
||||||
|
|
||||||
background-color: pvar(--bg);
|
background-color: pvar(--bg);
|
||||||
|
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.peertube-title {
|
.peertube-title {
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: var(--co-logo-size);
|
||||||
|
flex-grow: 1;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@include padding-left(18px);
|
@include padding-left(18px);
|
||||||
|
@include margin-right(0.5rem);
|
||||||
@include disable-default-a-behaviour;
|
@include disable-default-a-behaviour;
|
||||||
|
}
|
||||||
|
|
||||||
.instance-name {
|
.instance-name {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@include ellipsis;
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
.icon-logo {
|
||||||
display: none;
|
display: inline-block;
|
||||||
}
|
width: var(--co-logo-size);
|
||||||
}
|
height: var(--co-logo-size);
|
||||||
|
min-width: var(--co-logo-size);
|
||||||
|
max-width: var(--co-logo-size);
|
||||||
|
|
||||||
.icon-logo {
|
background-repeat: no-repeat;
|
||||||
display: inline-block;
|
background-size: contain;
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
min-width: 34px;
|
|
||||||
max-width: 34px;
|
|
||||||
|
|
||||||
background-repeat: no-repeat;
|
@include margin-left(18px);
|
||||||
|
@include margin-right(10px);
|
||||||
|
}
|
||||||
|
|
||||||
@include margin-left(18px);
|
my-search-typeahead {
|
||||||
@include margin-right(10px);
|
max-width: 270px;
|
||||||
}
|
|
||||||
|
@include margin-right(1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-button {
|
||||||
|
@include margin-right(0.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
display: none;
|
||||||
|
justify-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
z-index: #{z('menu') + 1} !important;
|
z-index: #{z('header') + 1} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
|
@ -64,11 +88,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-in-container {
|
.logged-in-container {
|
||||||
flex: 1;
|
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
transition: all .1s ease-in-out;
|
transition: all .1s ease-in-out;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.display-name {
|
.display-name {
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
|
@ -90,3 +114,95 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my-actor-avatar {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $menu-overlay-view) {
|
||||||
|
.peertube-title {
|
||||||
|
@include padding-left(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-logo {
|
||||||
|
@include margin-left(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $small-view) {
|
||||||
|
.root {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-search-typeahead {
|
||||||
|
@include margin-right(0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-button.tertiary-button {
|
||||||
|
@include margin-right(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
my-actor-avatar {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle {
|
||||||
|
@include peertube-button;
|
||||||
|
@include rounded-icon-button;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logged-in-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
.root {
|
||||||
|
--co-logo-size: 48px;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
display: grid;
|
||||||
|
row-gap: 0.5rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
right: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-search-typeahead {
|
||||||
|
grid-row: 2 !important;
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
max-width: none;
|
||||||
|
|
||||||
|
@include margin-right(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instance-name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CommonModule, ViewportScroller } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
import { Router, RouterLink } from '@angular/router'
|
import { Router, RouterLink } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
|
@ -9,11 +9,9 @@ import {
|
||||||
MenuService,
|
MenuService,
|
||||||
RedirectService,
|
RedirectService,
|
||||||
ScreenService,
|
ScreenService,
|
||||||
ServerService,
|
ServerService
|
||||||
UserService
|
|
||||||
} from '@app/core'
|
} from '@app/core'
|
||||||
import { NotificationDropdownComponent } from '@app/header/notification-dropdown.component'
|
import { NotificationDropdownComponent } from '@app/header/notification-dropdown.component'
|
||||||
import { scrollToTop } from '@app/helpers'
|
|
||||||
import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
|
import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
|
||||||
import { QuickSettingsModalComponent } from '@app/menu/quick-settings-modal.component'
|
import { QuickSettingsModalComponent } from '@app/menu/quick-settings-modal.component'
|
||||||
import { ActorAvatarComponent } from '@app/shared/shared-actor-image/actor-avatar.component'
|
import { ActorAvatarComponent } from '@app/shared/shared-actor-image/actor-avatar.component'
|
||||||
|
@ -73,22 +71,16 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
private authSub: Subscription
|
private authSub: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private viewportScroller: ViewportScroller,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private userService: UserService,
|
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private redirectService: RedirectService,
|
private redirectService: RedirectService,
|
||||||
private hotkeysService: HotkeysService,
|
private hotkeysService: HotkeysService,
|
||||||
private screenService: ScreenService,
|
private screenService: ScreenService,
|
||||||
private menuService: MenuService,
|
|
||||||
private modalService: PeertubeModalService,
|
private modalService: PeertubeModalService,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private menu: MenuService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get isInMobileView () {
|
|
||||||
return this.screenService.isInMobileView()
|
|
||||||
}
|
|
||||||
|
|
||||||
get language () {
|
get language () {
|
||||||
return this.languageChooserModal.getCurrentLanguage()
|
return this.languageChooserModal.getCurrentLanguage()
|
||||||
}
|
}
|
||||||
|
@ -101,6 +93,14 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
return this.serverConfig.instance.name
|
return this.serverConfig.instance.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInMobileView () {
|
||||||
|
return this.screenService.isInMobileView()
|
||||||
|
}
|
||||||
|
|
||||||
|
isInSmallView () {
|
||||||
|
return this.screenService.isInSmallView()
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
||||||
|
|
||||||
|
@ -170,57 +170,14 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
this.quickSettingsModal.show()
|
this.quickSettingsModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: needed?
|
|
||||||
onDropdownOpenChange (opened: boolean) {
|
|
||||||
if (this.screenService.isInMobileView()) return
|
|
||||||
|
|
||||||
// Close dropdown when window scroll to avoid dropdown quick jump for re-position
|
|
||||||
const onWindowScroll = () => {
|
|
||||||
this.dropdown?.close()
|
|
||||||
window.removeEventListener('scroll', onWindowScroll)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opened) {
|
|
||||||
window.addEventListener('scroll', onWindowScroll)
|
|
||||||
document.querySelector('nav').scrollTo(0, 0) // Reset menu scroll to easy lock
|
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
||||||
document.querySelector('nav').addEventListener('scroll', this.onMenuScrollEvent)
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
||||||
document.querySelector('nav').removeEventListener('scroll', this.onMenuScrollEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
|
|
||||||
// FIXME: needed?
|
|
||||||
onMenuScrollEvent () {
|
|
||||||
document.querySelector('nav').scrollTo(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: needed?
|
|
||||||
onActiveLinkScrollToAnchor (link: HTMLAnchorElement) {
|
|
||||||
const linkURL = link.getAttribute('href')
|
|
||||||
const linkHash = link.getAttribute('fragment')
|
|
||||||
|
|
||||||
// On same url without fragment restore top scroll position
|
|
||||||
if (!linkHash && this.router.url.includes(linkURL)) {
|
|
||||||
scrollToTop('smooth')
|
|
||||||
}
|
|
||||||
|
|
||||||
// On same url with fragment restore anchor scroll position
|
|
||||||
if (linkHash && this.router.url === linkURL) {
|
|
||||||
this.viewportScroller.scrollToAnchor(linkHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.screenService.isInSmallView()) {
|
|
||||||
this.menuService.toggleMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openHotkeysCheatSheet () {
|
openHotkeysCheatSheet () {
|
||||||
this.hotkeysService.cheatSheetToggle.next(!this.hotkeysHelpVisible)
|
this.hotkeysService.cheatSheetToggle.next(!this.hotkeysHelpVisible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleMenu () {
|
||||||
|
this.menu.toggleMenu()
|
||||||
|
}
|
||||||
|
|
||||||
private updateUserState () {
|
private updateUserState () {
|
||||||
this.user = this.loggedIn
|
this.user = this.loggedIn
|
||||||
? this.authService.getUser()
|
? this.authService.getUser()
|
||||||
|
|
|
@ -12,21 +12,23 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
@if (isInMobileView) {
|
@if (isInMobileView) {
|
||||||
<div i18n-title title="View your notifications" class="peertube-button tertiary-button rounded-icon-button">
|
<a
|
||||||
|
i18n-title title="View your notifications"
|
||||||
|
class="peertube-button tertiary-button rounded-icon-button notification-inbox-link"
|
||||||
|
routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)"
|
||||||
|
>
|
||||||
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
|
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
|
||||||
|
|
||||||
<a routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)">
|
<ng-container *ngTemplateOutlet="notificationIcon"></ng-container>
|
||||||
<ng-container *ngTemplateOutlet="notificationIcon"></ng-container>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
} @else {
|
} @else {
|
||||||
<div
|
<div
|
||||||
ngbDropdown autoClose="outside" placement="bottom" container="body" dropdownClass="dropdown-notifications"
|
ngbDropdown autoClose="outside" placement="bottom" container="body" dropdownClass="dropdown-notifications"
|
||||||
#dropdown="ngbDropdown" (shown)="onDropdownShown()" (hidden)="onDropdownHidden()"
|
#dropdown="ngbDropdown" (shown)="onDropdownShown()" (hidden)="onDropdownHidden()"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
i18n-title title="View your notifications" class="peertube-button tertiary-button rounded-icon-button disable-dropdown-caret"
|
i18n-title title="View your notifications" class="peertube-button tertiary-button rounded-icon-button disable-dropdown-caret notification-inbox-dropdown"
|
||||||
[ngClass]="{ 'notification-inbox-dropdown': true, 'shown': opened, 'hidden': isInMobileView }"
|
[ngClass]="{ 'shown': opened, 'hidden': isInMobileView }"
|
||||||
ngbDropdownToggle
|
ngbDropdownToggle
|
||||||
>
|
>
|
||||||
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
|
<ng-container *ngTemplateOutlet="notificationNumber"></ng-container>
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
.notification-inbox-link {
|
.notification-inbox-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
.unread-notifications {
|
.unread-notifications {
|
||||||
@include margin-left(20px);
|
@include margin-left(20px);
|
||||||
|
@ -91,24 +92,14 @@
|
||||||
top: 6px;
|
top: 6px;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
background-color: pvar(--primary);
|
background-color: pvar(--primary);
|
||||||
color: pvar(--on-primary);
|
color: pvar(--on-primary);
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
line-height: 1;
|
line-height: 16px;
|
||||||
|
padding: 0 3px;
|
||||||
@media screen and (max-width: $mobile-view) {
|
|
||||||
top: -4px;
|
|
||||||
left: -2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#search-video {
|
#search-video {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
@include peertube-input-text(270px);
|
@include peertube-input-text(100%);
|
||||||
|
|
||||||
& {
|
& {
|
||||||
padding-inline-start: 42px; // For the search icon
|
padding-inline-start: 42px; // For the search icon
|
||||||
|
@ -15,14 +15,6 @@
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: pvar(--input-placeholder);
|
color: pvar(--input-placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button {
|
.search-button {
|
||||||
|
|
|
@ -1,21 +1,43 @@
|
||||||
<div class="menu-container" [ngClass]="{ collapsed: collapsed }">
|
<ng-template #moreInfoButton>
|
||||||
|
<my-button i18n class="mt-2 d-block" theme="secondary" icon="help" i18n-ariaLabel aria-label="More info" i18n ptRouterLink="/about" ptRouterLinkActive="active">
|
||||||
|
@if (!collapsed) {
|
||||||
|
More info
|
||||||
|
}
|
||||||
|
</my-button>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="menu-container" [ngClass]="{ collapsed: collapsed, 'logged-in': loggedIn }">
|
||||||
|
|
||||||
<div class="main-menu-wrapper">
|
<div class="main-menu-wrapper">
|
||||||
<div class="main-menu-scrollbar">
|
<div class="main-menu-scrollbar">
|
||||||
|
|
||||||
<div class="main-menu">
|
<div class="main-menu">
|
||||||
|
<div class="mobile-controls">
|
||||||
|
<span class="icon-logo"></span>
|
||||||
|
|
||||||
|
<my-button rounded="true" icon="cross" theme="tertiary" (click)="toggleMenu()"></my-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toggle-menu-container">
|
<div class="toggle-menu-container">
|
||||||
@if (collapsed) {
|
@if (collapsed) {
|
||||||
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Display the lateral bar" (click)="toggleMenu()">
|
<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>
|
<my-global-icon class="transform-rotate-180" iconName="chevron-left"></my-global-icon>
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
<button type="button" class="button-unstyle toggle-menu" i18n-title title="Hide the lateral bar" (click)="toggleMenu()">
|
<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>
|
<my-global-icon iconName="chevron-left"></my-global-icon>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!loggedIn" class="about about-top">
|
||||||
|
<div i18n [ngClass]="{ 'visually-hidden': collapsed }" class="block-title me-2">{{ instanceName }}</div>
|
||||||
|
|
||||||
|
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
|
||||||
|
|
||||||
|
<ng-container *ngTemplateOutlet="moreInfoButton"></ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<ng-container *ngFor="let menuSection of menuSections" >
|
<ng-container *ngFor="let menuSection of menuSections" >
|
||||||
<ul class="ul-unstyle" [ngClass]="[ menuSection.key, 'menu-block' ]">
|
<ul class="ul-unstyle" [ngClass]="[ menuSection.key, 'menu-block' ]">
|
||||||
|
@ -43,16 +65,10 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="about">
|
<div *ngIf="loggedIn" class="about">
|
||||||
<div [ngClass]="{ 'visually-hidden': collapsed }" class="block-title">About {{ instanceName }}</div>
|
<div [ngClass]="{ 'visually-hidden': collapsed }" class="block-title">{{ instanceName }}</div>
|
||||||
|
|
||||||
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
|
<ng-container *ngTemplateOutlet="moreInfoButton"></ng-container>
|
||||||
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,3 +82,6 @@
|
||||||
<a class="d-block fs-8" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer" i18n>Discover more platforms</a>
|
<a class="d-block fs-8" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer" i18n>Discover more platforms</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->
|
||||||
|
<div class="menu-overlay" [ngClass]="{ 'menu-collapsed': collapsed }" (click)="toggleMenu()"></div>
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
z-index: z(menu);
|
z-index: z(menu);
|
||||||
width: calc(#{$menu-width} - 2rem);
|
width: calc(#{$menu-width} - 2rem);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: calc(100vh - #{$header-height});
|
height: calc(100vh - #{pvar(--header-height)});
|
||||||
|
|
||||||
@include margin-left(2rem);
|
@include margin-left(pvar(--menu-margin-left));
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
--co-menu-x-padding: 0.25rem;
|
--co-menu-x-padding: 0.25rem;
|
||||||
|
@ -20,14 +20,6 @@
|
||||||
|
|
||||||
@include margin-left(0);
|
@include margin-left(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
|
||||||
width: 100% !important;
|
|
||||||
|
|
||||||
.main-menu {
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-menu {
|
.toggle-menu {
|
||||||
|
@ -102,8 +94,10 @@
|
||||||
@include padding-right(var(--co-menu-x-padding));
|
@include padding-right(var(--co-menu-x-padding));
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-block,
|
.menu-block:not(:last-child),
|
||||||
.collapsed .toggle-menu-container {
|
.logged-in .menu-block,
|
||||||
|
.collapsed .toggle-menu-container,
|
||||||
|
.about-top {
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -113,11 +107,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed .menu-block,
|
.collapsed {
|
||||||
.collapsed .toggle-menu-container {
|
.menu-block,
|
||||||
&::after {
|
.toggle-menu-container,
|
||||||
width: 28px;
|
.about {
|
||||||
margin: 1rem auto;
|
&::after {
|
||||||
|
width: 28px;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +145,9 @@
|
||||||
color: pvar-fallback(--menu-fg, --fg-350);
|
color: pvar-fallback(--menu-fg, --fg-350);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
max-width: 180px;
|
||||||
|
|
||||||
|
@include ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-link {
|
.menu-link {
|
||||||
|
@ -201,3 +201,87 @@
|
||||||
my-button[theme=secondary] ::ng-deep my-global-icon {
|
my-button[theme=secondary] ::ng-deep my-global-icon {
|
||||||
color: pvar(--secondary-icon-color) !important;
|
color: pvar(--secondary-icon-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-overlay {
|
||||||
|
background-color: #000;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
opacity: 0.75;
|
||||||
|
content: '';
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: z(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-controls {
|
||||||
|
display: none;
|
||||||
|
padding: 0 calc(var(--co-menu-x-padding) - 1rem) 2rem var(--co-menu-x-padding);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.icon-logo {
|
||||||
|
display: inline-block;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $menu-overlay-view) {
|
||||||
|
// On small screen use a menu overlay
|
||||||
|
|
||||||
|
.menu-container {
|
||||||
|
&:not(.collapsed) {
|
||||||
|
--menu-margin-left: 0;
|
||||||
|
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: pvar-fallback(--menu-bg, --bg-secondary-400);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.main-menu-wrapper {
|
||||||
|
height: auto;
|
||||||
|
max-height: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-overlay:not(.menu-collapsed) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
.menu-container {
|
||||||
|
height: 100vh;
|
||||||
|
margin-top: calc(#{pvar(--header-height)} * -1);
|
||||||
|
z-index: z(root-header) + 1;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.collapsed) {
|
||||||
|
width: 100vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-menu {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-overlay {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-controls {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-menu-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
AuthUser,
|
AuthUser,
|
||||||
HooksService,
|
HooksService,
|
||||||
MenuService,
|
MenuService,
|
||||||
|
RedirectService,
|
||||||
ServerService,
|
ServerService,
|
||||||
UserService
|
UserService
|
||||||
} from '@app/core'
|
} from '@app/core'
|
||||||
|
@ -53,8 +54,8 @@ const debugLogger = debug('peertube:menu:MenuComponent')
|
||||||
})
|
})
|
||||||
export class MenuComponent implements OnInit, OnDestroy {
|
export class MenuComponent implements OnInit, OnDestroy {
|
||||||
menuSections: MenuSection[] = []
|
menuSections: MenuSection[] = []
|
||||||
|
loggedIn: boolean
|
||||||
|
|
||||||
private isLoggedIn: boolean
|
|
||||||
private user: AuthUser
|
private user: AuthUser
|
||||||
private canSeeVideoMakerBlock: boolean
|
private canSeeVideoMakerBlock: boolean
|
||||||
|
|
||||||
|
@ -67,7 +68,8 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private hooks: HooksService,
|
private hooks: HooksService,
|
||||||
private menu: MenuService
|
private menu: MenuService,
|
||||||
|
private redirectService: RedirectService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get shortDescription () {
|
get shortDescription () {
|
||||||
|
@ -82,13 +84,17 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
return this.menu.isCollapsed()
|
return this.menu.isCollapsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isOverlay () {
|
||||||
|
return this.menu.isCollapsed()
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.isLoggedIn = this.authService.isLoggedIn()
|
this.loggedIn = this.authService.isLoggedIn()
|
||||||
this.onUserStateChange()
|
this.onUserStateChange()
|
||||||
|
|
||||||
this.authSub = this.authService.loginChangedSource.subscribe(status => {
|
this.authSub = this.authService.loginChangedSource.subscribe(status => {
|
||||||
if (status === AuthStatus.LoggedIn) this.isLoggedIn = true
|
if (status === AuthStatus.LoggedIn) this.loggedIn = true
|
||||||
else if (status === AuthStatus.LoggedOut) this.isLoggedIn = false
|
else if (status === AuthStatus.LoggedOut) this.loggedIn = false
|
||||||
|
|
||||||
this.onUserStateChange()
|
this.onUserStateChange()
|
||||||
})
|
})
|
||||||
|
@ -127,9 +133,14 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
title: $localize`Quick access`,
|
title: $localize`Quick access`,
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
path: '/home',
|
path: this.redirectService.getDefaultRoute(),
|
||||||
icon: 'home' as GlobalIconName,
|
icon: 'home' as GlobalIconName,
|
||||||
label: $localize`Home`
|
label: $localize`Home`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/videos/subscriptions',
|
||||||
|
icon: 'subscriptions' as GlobalIconName,
|
||||||
|
label: $localize`Subscriptions`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -138,18 +149,13 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
private buildLibraryLinks (): MenuSection {
|
private buildLibraryLinks (): MenuSection {
|
||||||
let links: MenuLink[] = []
|
let links: MenuLink[] = []
|
||||||
|
|
||||||
if (this.isLoggedIn) {
|
if (this.loggedIn) {
|
||||||
links = links.concat([
|
links = links.concat([
|
||||||
{
|
{
|
||||||
path: '/my-library/video-playlists',
|
path: '/my-library/video-playlists',
|
||||||
icon: 'playlists' as GlobalIconName,
|
icon: 'playlists' as GlobalIconName,
|
||||||
label: $localize`Playlists`
|
label: $localize`Playlists`
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/videos/subscriptions',
|
|
||||||
icon: 'subscriptions' as GlobalIconName,
|
|
||||||
label: $localize`Subscriptions`
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/my-library/history/videos',
|
path: '/my-library/history/videos',
|
||||||
icon: 'history' as GlobalIconName,
|
icon: 'history' as GlobalIconName,
|
||||||
|
@ -168,7 +174,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
private buildVideoMakerLinks (): MenuSection {
|
private buildVideoMakerLinks (): MenuSection {
|
||||||
let links: MenuLink[] = []
|
let links: MenuLink[] = []
|
||||||
|
|
||||||
if (this.isLoggedIn && this.canSeeVideoMakerBlock) {
|
if (this.loggedIn && this.canSeeVideoMakerBlock) {
|
||||||
links = links.concat([
|
links = links.concat([
|
||||||
{
|
{
|
||||||
path: '/my-library/video-channels',
|
path: '/my-library/video-channels',
|
||||||
|
@ -202,7 +208,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
private buildAdminLinks (): MenuSection {
|
private buildAdminLinks (): MenuSection {
|
||||||
const links: MenuLink[] = []
|
const links: MenuLink[] = []
|
||||||
|
|
||||||
if (this.isLoggedIn) {
|
if (this.loggedIn) {
|
||||||
if (this.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
if (this.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
||||||
links.push({
|
links.push({
|
||||||
path: '/admin/overview',
|
path: '/admin/overview',
|
||||||
|
@ -238,7 +244,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
private computeCanSeeVideoMakerBlock () {
|
private computeCanSeeVideoMakerBlock () {
|
||||||
if (!this.isLoggedIn) return of(false)
|
if (!this.loggedIn) return of(false)
|
||||||
if (!this.user.hasUploadDisabled()) return of(true)
|
if (!this.user.hasUploadDisabled()) return of(true)
|
||||||
|
|
||||||
return this.authService.userInformationLoaded
|
return this.authService.userInformationLoaded
|
||||||
|
@ -256,7 +262,7 @@ export class MenuComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUserStateChange () {
|
private onUserStateChange () {
|
||||||
this.user = this.isLoggedIn
|
this.user = this.loggedIn
|
||||||
? this.authService.getUser()
|
? this.authService.getUser()
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export class AccountSetupWarningModalComponent {
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
|
|
||||||
private LOCAL_STORAGE_KEYS = {
|
private LS_KEYS = {
|
||||||
NO_ACCOUNT_SETUP_WARNING_MODAL: 'no_account_setup_warning_modal'
|
NO_ACCOUNT_SETUP_WARNING_MODAL: 'no_account_setup_warning_modal'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export class AccountSetupWarningModalComponent {
|
||||||
|
|
||||||
shouldOpen (user: User) {
|
shouldOpen (user: User) {
|
||||||
if (user.noAccountSetupWarningModal === true) return false
|
if (user.noAccountSetupWarningModal === true) return false
|
||||||
if (peertubeLocalStorage.getItem(this.LOCAL_STORAGE_KEYS.NO_ACCOUNT_SETUP_WARNING_MODAL) === 'true') return false
|
if (peertubeLocalStorage.getItem(this.LS_KEYS.NO_ACCOUNT_SETUP_WARNING_MODAL) === 'true') return false
|
||||||
|
|
||||||
if (this.hasAccountAvatar(user) && this.hasAccountDescription(user)) return false
|
if (this.hasAccountAvatar(user) && this.hasAccountDescription(user)) return false
|
||||||
if (this.userService.hasSignupInThisSession()) return false
|
if (this.userService.hasSignupInThisSession()) return false
|
||||||
|
@ -75,7 +75,7 @@ export class AccountSetupWarningModalComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private doNotOpenAgain () {
|
private doNotOpenAgain () {
|
||||||
peertubeLocalStorage.setItem(this.LOCAL_STORAGE_KEYS.NO_ACCOUNT_SETUP_WARNING_MODAL, 'true')
|
peertubeLocalStorage.setItem(this.LS_KEYS.NO_ACCOUNT_SETUP_WARNING_MODAL, 'true')
|
||||||
|
|
||||||
this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
|
this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
|
||||||
export class AdminWelcomeModalComponent {
|
export class AdminWelcomeModalComponent {
|
||||||
@ViewChild('modal', { static: true }) modal: ElementRef
|
@ViewChild('modal', { static: true }) modal: ElementRef
|
||||||
|
|
||||||
private LOCAL_STORAGE_KEYS = {
|
private LS_KEYS = {
|
||||||
NO_WELCOME_MODAL: 'no_welcome_modal'
|
NO_WELCOME_MODAL: 'no_welcome_modal'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export class AdminWelcomeModalComponent {
|
||||||
|
|
||||||
shouldOpen (user: User) {
|
shouldOpen (user: User) {
|
||||||
if (user.noWelcomeModal === true) return false
|
if (user.noWelcomeModal === true) return false
|
||||||
if (peertubeLocalStorage.getItem(this.LOCAL_STORAGE_KEYS.NO_WELCOME_MODAL) === 'true') return false
|
if (peertubeLocalStorage.getItem(this.LS_KEYS.NO_WELCOME_MODAL) === 'true') return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ export class AdminWelcomeModalComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
doNotOpenAgain () {
|
doNotOpenAgain () {
|
||||||
peertubeLocalStorage.setItem(this.LOCAL_STORAGE_KEYS.NO_WELCOME_MODAL, 'true')
|
peertubeLocalStorage.setItem(this.LS_KEYS.NO_WELCOME_MODAL, 'true')
|
||||||
|
|
||||||
this.userService.updateMyProfile({ noWelcomeModal: true })
|
this.userService.updateMyProfile({ noWelcomeModal: true })
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class InstanceConfigWarningModalComponent {
|
||||||
stopDisplayModal = false
|
stopDisplayModal = false
|
||||||
about: About
|
about: About
|
||||||
|
|
||||||
private LOCAL_STORAGE_KEYS = {
|
private LS_KEYS = {
|
||||||
NO_INSTANCE_CONFIG_WARNING_MODAL: 'no_instance_config_warning_modal'
|
NO_INSTANCE_CONFIG_WARNING_MODAL: 'no_instance_config_warning_modal'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export class InstanceConfigWarningModalComponent {
|
||||||
|
|
||||||
shouldOpenByUser (user: User) {
|
shouldOpenByUser (user: User) {
|
||||||
if (user.noInstanceConfigWarningModal === true) return false
|
if (user.noInstanceConfigWarningModal === true) return false
|
||||||
if (peertubeLocalStorage.getItem(this.LOCAL_STORAGE_KEYS.NO_INSTANCE_CONFIG_WARNING_MODAL) === 'true') return false
|
if (peertubeLocalStorage.getItem(this.LS_KEYS.NO_INSTANCE_CONFIG_WARNING_MODAL) === 'true') return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export class InstanceConfigWarningModalComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private doNotOpenAgain () {
|
private doNotOpenAgain () {
|
||||||
peertubeLocalStorage.setItem(this.LOCAL_STORAGE_KEYS.NO_INSTANCE_CONFIG_WARNING_MODAL, 'true')
|
peertubeLocalStorage.setItem(this.LS_KEYS.NO_INSTANCE_CONFIG_WARNING_MODAL, 'true')
|
||||||
|
|
||||||
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
|
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-focus-within:focus-within {
|
.button-focus-within:focus-within {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-focus-within:focus-within {
|
.button-focus-within:focus-within {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
|
|
|
@ -81,13 +81,13 @@ $input-border-radius: 3px;
|
||||||
&.maximized {
|
&.maximized {
|
||||||
z-index: #{z(root-header) - 1};
|
z-index: #{z(root-header) - 1};
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: $header-height;
|
top: pvar(--header-height);
|
||||||
left: $menu-width;
|
left: $menu-width;
|
||||||
|
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
width: calc(100% - #{$menu-width});
|
width: calc(100% - #{$menu-width});
|
||||||
height: calc(100vh - #{$header-height});
|
height: calc(100vh - #{pvar(--header-height)});
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
|
|
|
@ -9,7 +9,7 @@ p-inputmask {
|
||||||
|
|
||||||
&:focus-within,
|
&:focus-within,
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
|
|
|
@ -16,6 +16,7 @@ const icons = {
|
||||||
'flame': require('../../../assets/images/misc/flame.svg'),
|
'flame': require('../../../assets/images/misc/flame.svg'),
|
||||||
|
|
||||||
// feather/lucide icons
|
// feather/lucide icons
|
||||||
|
'menu': require('../../../assets/images/feather/menu.svg'),
|
||||||
'history': require('../../../assets/images/feather/history.svg'),
|
'history': require('../../../assets/images/feather/history.svg'),
|
||||||
'registry': require('../../../assets/images/feather/registry.svg'),
|
'registry': require('../../../assets/images/feather/registry.svg'),
|
||||||
'subscriptions': require('../../../assets/images/feather/subscriptions.svg'),
|
'subscriptions': require('../../../assets/images/feather/subscriptions.svg'),
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
<table *ngIf="serverConfig">
|
<table *ngIf="serverConfig">
|
||||||
<caption i18n>Features found on this instance</caption>
|
<caption i18n>Features found on this instance</caption>
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" scope="row">PeerTube version</th>
|
<th i18n class="t-label" scope="row">PeerTube version</th>
|
||||||
|
|
||||||
<td class="value">{{ getServerVersionAndCommit() }}</td>
|
<td class="value">{{ getServerVersionAndCommit() }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="label" scope="row">
|
<th class="t-label" scope="row">
|
||||||
<div i18n>Default NSFW/sensitive videos policy</div>
|
<div i18n>Default NSFW/sensitive videos policy</div>
|
||||||
<span i18n class="fs-7 fw-normal fst-italic">can be redefined by the users</span>
|
<span i18n class="fs-7 fw-normal fst-italic">can be redefined by the users</span>
|
||||||
</th>
|
</th>
|
||||||
|
@ -18,31 +18,31 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" scope="row">User registration</th>
|
<th i18n class="t-label" scope="row">User registration</th>
|
||||||
|
|
||||||
<td class="value">{{ buildRegistrationLabel() }}</td>
|
<td class="value">{{ buildRegistrationLabel() }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">Video uploads</th>
|
<th i18n class="t-label" colspan="2">Video uploads</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Transcoding in multiple resolutions</th>
|
<th i18n class="t-sub-label" scope="row">Transcoding in multiple resolutions</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Automatic transcription</th>
|
<th i18n class="t-sub-label" scope="row">Automatic transcription</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.videoTranscription.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.videoTranscription.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Video uploads</th>
|
<th i18n class="t-sub-label" scope="row">Video uploads</th>
|
||||||
<td>
|
<td>
|
||||||
<span i18n *ngIf="serverConfig.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
|
<span i18n *ngIf="serverConfig.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
|
||||||
<span i18n *ngIf="!serverConfig.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
|
<span i18n *ngIf="!serverConfig.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Video quota</th>
|
<th i18n class="t-sub-label" scope="row">Video quota</th>
|
||||||
|
|
||||||
<td class="value">
|
<td class="value">
|
||||||
<ng-container *ngIf="initialUserVideoQuota !== -1">
|
<ng-container *ngIf="initialUserVideoQuota !== -1">
|
||||||
|
@ -70,90 +70,90 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">Live streaming</th>
|
<th i18n class="t-label" colspan="2">Live streaming</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Live streaming enabled</th>
|
<th i18n class="t-sub-label" scope="row">Live streaming enabled</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.live.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.live.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr *ngIf="serverConfig.live.enabled">
|
<tr *ngIf="serverConfig.live.enabled">
|
||||||
<th i18n class="sub-label" scope="row">Transcode live video in multiple resolutions</th>
|
<th i18n class="t-sub-label" scope="row">Transcode live video in multiple resolutions</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.live.transcoding.enabled && serverConfig.live.transcoding.enabledResolutions.length > 1"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.live.transcoding.enabled && serverConfig.live.transcoding.enabledResolutions.length > 1"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr *ngIf="serverConfig.live.enabled">
|
<tr *ngIf="serverConfig.live.enabled">
|
||||||
<th i18n class="sub-label" scope="row">Max parallel lives</th>
|
<th i18n class="t-sub-label" scope="row">Max parallel lives</th>
|
||||||
<td i18n>
|
<td i18n>
|
||||||
{{ maxUserLives }} per user / {{ maxInstanceLives }} per instance
|
{{ maxUserLives }} per user / {{ maxInstanceLives }} per instance
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">Video Import</th>
|
<th i18n class="t-label" colspan="2">Video Import</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">HTTP import (YouTube, Vimeo, direct URL...)</th>
|
<th i18n class="t-sub-label" scope="row">HTTP import (YouTube, Vimeo, direct URL...)</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.import.videos.http.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.import.videos.http.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Torrent import</th>
|
<th i18n class="t-sub-label" scope="row">Torrent import</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.import.videos.torrent.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.import.videos.torrent.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Channel synchronization with other platforms (YouTube, Vimeo, ...)</th>
|
<th i18n class="t-sub-label" scope="row">Channel synchronization with other platforms (YouTube, Vimeo, ...)</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.import.videoChannelSynchronization.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.import.videoChannelSynchronization.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">User Import/Export</th>
|
<th i18n class="t-label" colspan="2">User Import/Export</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Users can export their data</th>
|
<th i18n class="t-sub-label" scope="row">Users can export their data</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.export.users.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.export.users.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Users can import their data</th>
|
<th i18n class="t-sub-label" scope="row">Users can import their data</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.import.users.enabled"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.import.users.enabled"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">Search</th>
|
<th i18n class="t-label" colspan="2">Search</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Users can resolve distant content</th>
|
<th i18n class="t-sub-label" scope="row">Users can resolve distant content</th>
|
||||||
<td>
|
<td>
|
||||||
<my-feature-boolean [value]="serverConfig.search.remoteUri.users"></my-feature-boolean>
|
<my-feature-boolean [value]="serverConfig.search.remoteUri.users"></my-feature-boolean>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="label" colspan="2">Plugins & Themes</th>
|
<th i18n class="t-label" colspan="2">Plugins & Themes</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Available themes</th>
|
<th i18n class="t-sub-label" scope="row">Available themes</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="theme" *ngFor="let theme of serverConfig.theme.registered">
|
<span class="theme" *ngFor="let theme of serverConfig.theme.registered">
|
||||||
{{ theme.name }}
|
{{ theme.name }}
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n class="sub-label" scope="row">Plugins enabled</th>
|
<th i18n class="t-sub-label" scope="row">Plugins enabled</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="plugin" *ngFor="let plugin of serverConfig.plugin.registered">
|
<span class="plugin" *ngFor="let plugin of serverConfig.plugin.registered">
|
||||||
{{ plugin.name }}
|
{{ plugin.name }}
|
||||||
|
|
|
@ -6,13 +6,13 @@ table {
|
||||||
color: pvar(--fg);
|
color: pvar(--fg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.label,
|
.t-label,
|
||||||
.sub-label {
|
.t-sub-label {
|
||||||
&.label {
|
&.t-label {
|
||||||
font-weight: $font-semibold;
|
font-weight: $font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sub-label {
|
&.t-sub-label {
|
||||||
font-weight: $font-regular;
|
font-weight: $font-regular;
|
||||||
|
|
||||||
@include padding-left(30px);
|
@include padding-left(30px);
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class InstanceFollowService {
|
||||||
|
|
||||||
getFollowing (options: {
|
getFollowing (options: {
|
||||||
pagination: RestPagination
|
pagination: RestPagination
|
||||||
sort: SortMeta
|
sort?: SortMeta
|
||||||
search?: string
|
search?: string
|
||||||
actorType?: ActivityPubActorType
|
actorType?: ActivityPubActorType
|
||||||
state?: FollowState
|
state?: FollowState
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a
|
<a
|
||||||
class="action-button"
|
class="action-button"
|
||||||
[ngClass]="classes" [ngbTooltip]="title"
|
[ngClass]="classes" [ngbTooltip]="title"
|
||||||
[routerLink]="ptRouterLink" [queryParams]="ptQueryParams" [queryParamsHandling]="ptQueryParamsHandling"
|
[routerLink]="ptRouterLink" [queryParams]="ptQueryParams" [queryParamsHandling]="ptQueryParamsHandling" [routerLinkActive]="ptRouterLinkActive"
|
||||||
>
|
>
|
||||||
<ng-container *ngTemplateOutlet="content"></ng-container>
|
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -34,7 +34,8 @@ const debugLogger = debug('peertube:button')
|
||||||
RouterLink,
|
RouterLink,
|
||||||
LoaderComponent,
|
LoaderComponent,
|
||||||
GlobalIconComponent,
|
GlobalIconComponent,
|
||||||
ObserversModule
|
ObserversModule,
|
||||||
|
RouterLinkActive
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ export class ButtonComponent implements OnChanges, AfterViewInit {
|
||||||
@Input() ptRouterLink: string[] | string
|
@Input() ptRouterLink: string[] | string
|
||||||
@Input() ptQueryParams: Params
|
@Input() ptQueryParams: Params
|
||||||
@Input() ptQueryParamsHandling: QueryParamsHandling
|
@Input() ptQueryParamsHandling: QueryParamsHandling
|
||||||
|
@Input() ptRouterLinkActive = ''
|
||||||
|
|
||||||
@Input() title: string
|
@Input() title: string
|
||||||
@Input({ transform: booleanAttribute }) active = false
|
@Input({ transform: booleanAttribute }) active = false
|
||||||
|
|
|
@ -5,6 +5,4 @@ my-global-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
color: pvar(--on-primary);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<ng-template #content>
|
<ng-template #content>
|
||||||
|
<my-global-icon *ngIf="icon" [iconName]="icon"></my-global-icon>
|
||||||
|
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common'
|
||||||
import { Component, Input, OnInit } from '@angular/core'
|
import { Component, Input, OnInit } from '@angular/core'
|
||||||
import { RouterLink } from '@angular/router'
|
import { RouterLink } from '@angular/router'
|
||||||
import { NgIf, NgClass, NgTemplateOutlet } from '@angular/common'
|
import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-link',
|
selector: 'my-link',
|
||||||
styleUrls: [ './link.component.scss' ],
|
styleUrls: [ './link.component.scss' ],
|
||||||
templateUrl: './link.component.html',
|
templateUrl: './link.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ NgIf, RouterLink, NgClass, NgTemplateOutlet ]
|
imports: [ NgIf, RouterLink, NgClass, NgTemplateOutlet, GlobalIconComponent ]
|
||||||
})
|
})
|
||||||
export class LinkComponent implements OnInit {
|
export class LinkComponent implements OnInit {
|
||||||
@Input() internalLink?: string | any[]
|
@Input() internalLink?: string | any[]
|
||||||
|
@ -24,6 +25,8 @@ export class LinkComponent implements OnInit {
|
||||||
|
|
||||||
@Input() ariaLabel: string
|
@Input() ariaLabel: string
|
||||||
|
|
||||||
|
@Input() icon: GlobalIconName
|
||||||
|
|
||||||
builtClasses: string
|
builtClasses: string
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div class="parent-container">
|
<div class="parent-container">
|
||||||
<my-list-overflow [items]="menuEntries" [itemTemplate]="entryTemplate"></my-list-overflow>
|
<my-list-overflow [items]="menuEntries" [itemTemplate]="entryTemplate" hasBorder="true"></my-list-overflow>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (children) {
|
@if (children && children.length !== 0) {
|
||||||
<div class="children-container">
|
<div class="children-container">
|
||||||
<my-list-overflow [items]="children" [itemTemplate]="entryTemplate"></my-list-overflow>
|
<my-list-overflow [items]="children" [itemTemplate]="entryTemplate"></my-list-overflow>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,8 +20,6 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.parent-container {
|
.parent-container {
|
||||||
border-bottom: 1px solid pvar(--fg-200);
|
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
color: pvar(--fg-100);
|
color: pvar(--fg-100);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -62,16 +60,20 @@ h1 {
|
||||||
|
|
||||||
@include rfs(margin-bottom, 2.5rem);
|
@include rfs(margin-bottom, 2.5rem);
|
||||||
|
|
||||||
::ng-deep li:not(:last-child)::after {
|
::ng-deep li:not(:last-child) {
|
||||||
content: '•';
|
white-space: nowrap;
|
||||||
|
|
||||||
color: pvar(--secondary-icon-color);
|
&::after {
|
||||||
|
content: '•';
|
||||||
|
|
||||||
display: inline-block;
|
color: pvar(--secondary-icon-color);
|
||||||
margin: 0 0.5rem;
|
|
||||||
|
|
||||||
position: relative;
|
display: inline-block;
|
||||||
top: -1px;
|
margin: 0 0.5rem;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'
|
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'
|
||||||
import { NavigationEnd, Router, RouterModule } from '@angular/router'
|
import { ActivatedRoute, NavigationEnd, Router, RouterModule } 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'
|
||||||
import { logger } from '@root-helpers/logger'
|
import { logger } from '@root-helpers/logger'
|
||||||
import { filter, Subscription } from 'rxjs'
|
import { filter, Subscription } from 'rxjs'
|
||||||
|
@ -51,7 +51,7 @@ export class HorizontalMenuComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
private routerSub: Subscription
|
private routerSub: Subscription
|
||||||
|
|
||||||
constructor (private router: Router) {
|
constructor (private router: Router, private route: ActivatedRoute) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +74,15 @@ export class HorizontalMenuComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.activeParent = undefined
|
this.activeParent = undefined
|
||||||
|
|
||||||
const currentUrl = window.location.pathname
|
const currentUrl = window.location.pathname
|
||||||
|
const currentComponentPath = this.route.snapshot.pathFromRoot.reduce((a, c) => {
|
||||||
|
if (c.url.length === 0) return a
|
||||||
|
|
||||||
|
return a + '/' + c.url[0].path
|
||||||
|
}, '')
|
||||||
|
|
||||||
const entry = this.menuEntries.find(parent => {
|
const entry = this.menuEntries.find(parent => {
|
||||||
if (currentUrl.startsWith(parent.routerLink)) return true
|
if (currentUrl.startsWith(parent.routerLink)) return true
|
||||||
|
if (!parent.routerLink.startsWith('/') && `${currentComponentPath}/${parent.routerLink}` === currentUrl) return true
|
||||||
|
|
||||||
if (parent.children) return parent.children.some(child => currentUrl.startsWith(child.routerLink))
|
if (parent.children) return parent.children.some(child => currentUrl.startsWith(child.routerLink))
|
||||||
|
|
||||||
|
@ -84,7 +91,7 @@ export class HorizontalMenuComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
if (this.menuEntries.length !== 0) {
|
if (this.menuEntries.length !== 0) {
|
||||||
logger.info(`Unable to find entry for ${currentUrl}`, { menuEntries: this.menuEntries })
|
logger.info(`Unable to find entry for ${currentUrl} or ${currentComponentPath}`, { menuEntries: this.menuEntries })
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,54 +1,56 @@
|
||||||
<div #itemsParent class="list-overflow-parent" [ngClass]="{ 'opacity-0': !initialized }">
|
<div class="root">
|
||||||
|
<div #itemsParent class="list-overflow-parent" [ngClass]="{ 'opacity-0': !initialized, 'has-border': hasBorder }">
|
||||||
|
|
||||||
<ul class="ul-unstyle d-flex">
|
<ul class="ul-unstyle d-flex">
|
||||||
@for (item of items; track item.label; let id = $index) {
|
@for (item of items; track item.label; let id = $index) {
|
||||||
@if (!item.isDisplayed || item.isDisplayed()) {
|
|
||||||
<li class="d-inline-block" [id]="getId(id)" #itemsRendered>
|
|
||||||
<ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
@if (isMenuDisplayed()) {
|
|
||||||
@if (isInMobileView) {
|
|
||||||
<button type="button" class="peertube-button tertiary-button list-overflow-menu" (click)="toggleModal()">
|
|
||||||
<span class="chevron-down"></span>
|
|
||||||
</button>
|
|
||||||
} @else {
|
|
||||||
<div class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown">
|
|
||||||
<button class="peertube-button tertiary-button p-0" ngbDropdownToggle type="button">
|
|
||||||
<span class="chevron-down"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ul class="ul-unstyle" ngbDropdownMenu>
|
|
||||||
@for (item of items | slice:showItemsUntilIndexExcluded:items.length; track item.label) {
|
|
||||||
@if (!item.isDisplayed || item.isDisplayed()) {
|
|
||||||
<li>
|
|
||||||
<a [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
|
|
||||||
{{ item.label }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div >
|
|
||||||
|
|
||||||
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
|
||||||
<div class="modal-body">
|
|
||||||
<ul class="ul-unstyle">
|
|
||||||
@for (item of items | slice:showItemsUntilIndexExcluded:items.length; track item.label) {
|
|
||||||
@if (!item.isDisplayed || item.isDisplayed()) {
|
@if (!item.isDisplayed || item.isDisplayed()) {
|
||||||
<li>
|
<li class="d-inline-block" [id]="getId(id)" #itemsRendered>
|
||||||
<a [routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
|
<ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
|
||||||
{{ item.label }}
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
</ng-template>
|
@if (isMenuDisplayed()) {
|
||||||
|
@if (isInMobileView) {
|
||||||
|
<button type="button" class="peertube-button tertiary-button p-0 list-overflow-menu" (click)="toggleModal()">
|
||||||
|
<span class="chevron-down"></span>
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<div class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown">
|
||||||
|
<button class="peertube-button tertiary-button p-0" ngbDropdownToggle type="button">
|
||||||
|
<span class="chevron-down"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul class="ul-unstyle" ngbDropdownMenu>
|
||||||
|
@for (item of items | slice:showItemsUntilIndexExcluded:items.length; track item.label) {
|
||||||
|
@if (!item.isDisplayed || item.isDisplayed()) {
|
||||||
|
<li>
|
||||||
|
<a [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
|
||||||
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div >
|
||||||
|
|
||||||
|
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
||||||
|
<div class="modal-body">
|
||||||
|
<ul class="ul-unstyle">
|
||||||
|
@for (item of items | slice:showItemsUntilIndexExcluded:items.length; track item.label) {
|
||||||
|
@if (!item.isDisplayed || item.isDisplayed()) {
|
||||||
|
<li>
|
||||||
|
<a [routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
|
||||||
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
|
@ -10,13 +10,22 @@ li {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: calc(100vw - 30px);
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.list-overflow-parent {
|
.list-overflow-parent {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
// For the menu icon
|
// For the menu icon
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: calc(100vw - 30px);
|
|
||||||
|
&.has-border {
|
||||||
|
border-bottom: 1px solid pvar(--fg-200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-overflow-menu {
|
.list-overflow-menu {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { NgClass, NgFor, NgIf, NgTemplateOutlet, SlicePipe } from '@angular/common'
|
import { NgClass, NgTemplateOutlet, SlicePipe } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
|
booleanAttribute,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
|
@ -46,6 +47,7 @@ export interface ListOverflowItem {
|
||||||
export class ListOverflowComponent<T extends ListOverflowItem> implements AfterViewInit {
|
export class ListOverflowComponent<T extends ListOverflowItem> implements AfterViewInit {
|
||||||
@Input() items: T[]
|
@Input() items: T[]
|
||||||
@Input() itemTemplate: TemplateRef<{ item: T }>
|
@Input() itemTemplate: TemplateRef<{ item: T }>
|
||||||
|
@Input({ transform: booleanAttribute }) hasBorder = false
|
||||||
|
|
||||||
@ViewChild('modal', { static: true }) modal: ElementRef
|
@ViewChild('modal', { static: true }) modal: ElementRef
|
||||||
@ViewChild('itemsParent', { static: true }) parent: ElementRef<HTMLDivElement>
|
@ViewChild('itemsParent', { static: true }) parent: ElementRef<HTMLDivElement>
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
<ul class="sub-menu ul-unstyle" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }">
|
|
||||||
<ng-container *ngFor="let menuEntry of menuEntries; index as id">
|
|
||||||
@if (isDisplayed(menuEntry)) {
|
|
||||||
@if (menuEntry.routerLink) {
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
class="sub-menu-entry" [routerLink]="menuEntry.routerLink" routerLinkActive="active"
|
|
||||||
#routerLink (click)="onActiveLinkScrollToTop(routerLink)" ariaCurrentWhenActive="page"
|
|
||||||
>{{ menuEntry.label }}</a>
|
|
||||||
</li>
|
|
||||||
} @else if (isInSmallView) { <!-- On mobile, use a modal to display sub menu items -->
|
|
||||||
<li>
|
|
||||||
<button class="sub-menu-entry" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" (click)="openModal(id)">
|
|
||||||
{{ menuEntry.label }}
|
|
||||||
|
|
||||||
<span class="chevron-down"></span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
} @else {
|
|
||||||
<!-- On desktop, use a classic dropdown -->
|
|
||||||
<li ngbDropdown #dropdown="ngbDropdown" autoClose="true" container="body">
|
|
||||||
<button ngbDropdownToggle class="sub-menu-entry" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }">{{ menuEntry.label }}</button>
|
|
||||||
|
|
||||||
<ul class="ul-unstyle" ngbDropdownMenu>
|
|
||||||
<li *ngFor="let menuChild of menuEntry.children">
|
|
||||||
<a
|
|
||||||
*ngIf="isDisplayed(menuChild)" ngbDropdownItem
|
|
||||||
routerLinkActive="active" ariaCurrentWhenActive="page"
|
|
||||||
[routerLink]="menuChild.routerLink" #routerLink (click)="onActiveLinkScrollToTop(routerLink)"
|
|
||||||
[queryParams]="menuChild.queryParams"
|
|
||||||
>
|
|
||||||
<my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
|
|
||||||
|
|
||||||
{{ menuChild.label }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ng-container>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
|
||||||
<div class="modal-body">
|
|
||||||
<ng-container *ngFor="let menuEntry of menuEntries; index as id">
|
|
||||||
|
|
||||||
<div [ngClass]="{ hidden: id !== currentMenuEntryIndex }">
|
|
||||||
<ng-container *ngFor="let menuChild of menuEntry.children">
|
|
||||||
<a
|
|
||||||
*ngIf="isDisplayed(menuChild)" [ngClass]="{ icon: hasIcons }" [routerLink]="menuChild.routerLink" routerLinkActive="active"
|
|
||||||
#routerLink (click)="dismissOtherModals(); onActiveLinkScrollToTop(routerLink)" ariaCurrentWhenActive="page"
|
|
||||||
>
|
|
||||||
<my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
|
|
||||||
|
|
||||||
{{ menuChild.label }}
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
|
@ -1,50 +0,0 @@
|
||||||
@use '_variables' as *;
|
|
||||||
@use '_mixins' as *;
|
|
||||||
|
|
||||||
.sub-menu ::ng-deep .dropdown-toggle::after {
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-menu ::ng-deep .dropdown-menu {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu .dropdown-item {
|
|
||||||
top: -1px;
|
|
||||||
|
|
||||||
@include dropdown-with-icon-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-menu.no-scroll {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
my-global-icon {
|
|
||||||
@include margin-right(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: currentColor;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 9px 12px;
|
|
||||||
text-align: initial;
|
|
||||||
text-transform: unset;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@include disable-default-a-behaviour;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: pvar(--on-primary) !important;
|
|
||||||
background-color: pvar(--primary-450);
|
|
||||||
opacity: .9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
import { NgClass, NgFor, NgIf } from '@angular/common'
|
|
||||||
import { Component, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
|
||||||
import { NavigationEnd, Router, RouterLink, RouterLinkActive } from '@angular/router'
|
|
||||||
import { ScreenService } from '@app/core'
|
|
||||||
import { scrollToTop } from '@app/helpers'
|
|
||||||
import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
|
|
||||||
import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
import { filter } from 'rxjs/operators'
|
|
||||||
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
|
|
||||||
|
|
||||||
export type TopMenuDropdownParam = {
|
|
||||||
label: string
|
|
||||||
routerLink?: string
|
|
||||||
isDisplayed?: () => boolean // Default: () => true
|
|
||||||
|
|
||||||
children?: {
|
|
||||||
label: string
|
|
||||||
routerLink: string
|
|
||||||
queryParams?: { [id: string]: string }
|
|
||||||
iconName: GlobalIconName
|
|
||||||
|
|
||||||
isDisplayed?: () => boolean // Default: () => true
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-top-menu-dropdown',
|
|
||||||
templateUrl: './top-menu-dropdown.component.html',
|
|
||||||
styleUrls: [ './top-menu-dropdown.component.scss' ],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
NgClass,
|
|
||||||
NgFor,
|
|
||||||
NgIf,
|
|
||||||
RouterLinkActive,
|
|
||||||
RouterLink,
|
|
||||||
NgbDropdown,
|
|
||||||
NgbDropdownToggle,
|
|
||||||
NgbDropdownMenu,
|
|
||||||
NgbDropdownItem,
|
|
||||||
GlobalIconComponent
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class TopMenuDropdownComponent implements OnInit, OnChanges, OnDestroy {
|
|
||||||
@Input() menuEntries: TopMenuDropdownParam[] = []
|
|
||||||
|
|
||||||
@ViewChild('modal', { static: true }) modal: NgbModal
|
|
||||||
|
|
||||||
suffixLabels: { [ parentLabel: string ]: string }
|
|
||||||
hasIcons = false
|
|
||||||
isModalOpened = false
|
|
||||||
currentMenuEntryIndex: number
|
|
||||||
|
|
||||||
private routeSub: Subscription
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private router: Router,
|
|
||||||
private modalService: NgbModal,
|
|
||||||
private screen: ScreenService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
get isInSmallView () {
|
|
||||||
return this.screen.isInSmallView()
|
|
||||||
}
|
|
||||||
|
|
||||||
get isBroadcastMessageDisplayed () {
|
|
||||||
return this.screen.isBroadcastMessageDisplayed
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.updateChildLabels(window.location.pathname)
|
|
||||||
|
|
||||||
this.routeSub = this.router.events
|
|
||||||
.pipe(filter(event => event instanceof NavigationEnd))
|
|
||||||
.subscribe(() => this.updateChildLabels(window.location.pathname))
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges () {
|
|
||||||
this.updateChildLabels(window.location.pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy () {
|
|
||||||
if (this.routeSub) this.routeSub.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
dropdownAnchorClicked (dropdown: NgbDropdown) {
|
|
||||||
return dropdown.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal (index: number) {
|
|
||||||
this.currentMenuEntryIndex = index
|
|
||||||
this.isModalOpened = true
|
|
||||||
|
|
||||||
this.modalService.open(this.modal, {
|
|
||||||
centered: true,
|
|
||||||
beforeDismiss: () => {
|
|
||||||
this.onModalDismiss()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onModalDismiss () {
|
|
||||||
this.isModalOpened = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onActiveLinkScrollToTop (link: HTMLAnchorElement) {
|
|
||||||
if (!this.isBroadcastMessageDisplayed && this.router.url.includes(link.getAttribute('href'))) {
|
|
||||||
scrollToTop('smooth')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dismissOtherModals () {
|
|
||||||
this.modalService.dismissAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
isDisplayed (obj: { isDisplayed?: () => boolean }) {
|
|
||||||
if (typeof obj.isDisplayed !== 'function') return true
|
|
||||||
|
|
||||||
return obj.isDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateChildLabels (path: string) {
|
|
||||||
this.suffixLabels = {}
|
|
||||||
|
|
||||||
for (const entry of this.menuEntries) {
|
|
||||||
if (!entry.children) continue
|
|
||||||
|
|
||||||
for (const child of entry.children) {
|
|
||||||
if (path.startsWith(child.routerLink)) {
|
|
||||||
this.suffixLabels[entry.label] = child.label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +1,3 @@
|
||||||
<my-link i18n internalLink="/login" [href]="getExternalLoginHref()" [className]="className">{{ label }}</my-link>
|
<my-link class="d-flex" internalLink="/login" [href]="getExternalLoginHref()" [className]="className" [icon]="icon ? 'sign-in' : undefined">
|
||||||
|
{{ label }}
|
||||||
|
</my-link>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
@use '_variables' as *;
|
||||||
|
@use '_mixins' as *;
|
||||||
|
|
||||||
|
my-global-icon {
|
||||||
|
@include global-icon-size(24px);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, Input } from '@angular/core'
|
import { booleanAttribute, Component, Input } from '@angular/core'
|
||||||
import { ServerService } from '@app/core'
|
import { ServerService } from '@app/core'
|
||||||
import { PluginsManager } from '@root-helpers/plugins-manager'
|
import { PluginsManager } from '@root-helpers/plugins-manager'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
|
@ -7,11 +7,13 @@ import { LinkComponent } from '../common/link.component'
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-login-link',
|
selector: 'my-login-link',
|
||||||
templateUrl: './login-link.component.html',
|
templateUrl: './login-link.component.html',
|
||||||
|
styleUrls: [ './login-link.component.scss' ],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ LinkComponent ]
|
imports: [ LinkComponent ]
|
||||||
})
|
})
|
||||||
export class LoginLinkComponent {
|
export class LoginLinkComponent {
|
||||||
@Input() label = $localize`Login`
|
@Input() label = $localize`Login`
|
||||||
|
@Input({ transform: booleanAttribute }) icon = false
|
||||||
|
|
||||||
@Input() className?: string
|
@Input() className?: string
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,11 @@ export class VideoThumbnailComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
isLiveStreaming () {
|
isLiveStreaming () {
|
||||||
return this.video.isLive && this.video.state?.id === VideoState.PUBLISHED
|
// In non moderator mode we only display published live
|
||||||
|
// If in moderator mode, the server adds the state info to the object
|
||||||
|
if (!this.video.isLive) return false
|
||||||
|
|
||||||
|
return !this.video.state || this.video.state?.id === VideoState.PUBLISHED
|
||||||
}
|
}
|
||||||
|
|
||||||
isEndedLive () {
|
isEndedLive () {
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
<div class="root" [formGroup]="form">
|
<div class="root" [formGroup]="form">
|
||||||
|
|
||||||
<div class="first-row">
|
<div class="scope-row" *ngIf="totalFollowing && !hideScope">
|
||||||
<div>
|
@if (filters.scope === 'local') {
|
||||||
|
<ng-container i18n>Videos on {{ instanceName }}</ng-container>
|
||||||
|
} @else {
|
||||||
|
<ng-container i18n>Videos on {{ instanceName }} and {{ totalFollowing }} other platforms</ng-container>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="filters-row">
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
<div class="d-flex flex-wrap me-2">
|
<div class="d-flex flex-wrap me-2">
|
||||||
@for (quickFilter of quickFilters; track quickFilter) {
|
@for (quickFilter of quickFilters; track quickFilter) {
|
||||||
<my-button
|
<my-button
|
||||||
|
@ -37,25 +46,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap align-items-center ms-3" *ngIf="!hideScope">
|
<div class="d-flex flex-wrap align-items-center ms-3" >
|
||||||
<label for="scope" i18n class="select-label">Display videos of:</label>
|
<label for="sort-videos" i18n class="select-label">Sort by:</label>
|
||||||
|
|
||||||
<my-select-options inputId="scope" class="scope-select" formControlName="scope" [items]="availableScopes"></my-select-options>
|
<my-select-options inputId="sort-videos" class="d-inline-block me-2" formControlName="sort" [items]="sortItems"></my-select-options>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [ngbCollapse]="areFiltersCollapsed" [animation]="true">
|
<div [ngbCollapse]="areFiltersCollapsed" [animation]="true">
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<div class="section">
|
|
||||||
<div class="section-title" i18n>Display order</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sort-videos" i18n class="select-label">Sort by:</label>
|
|
||||||
|
|
||||||
<my-select-options inputId="sort-videos" class="d-inline-block me-2" formControlName="sort" [items]="sortItems"></my-select-options>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<ng-container i18n>Content preferences</ng-container>
|
<ng-container i18n>Content preferences</ng-container>
|
||||||
|
@ -90,6 +89,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="section" *ngIf="totalFollowing && !hideScope">
|
||||||
|
<div class="section-title">
|
||||||
|
<ng-container i18n>Platforms order</ng-container>
|
||||||
|
|
||||||
|
<div class="with-description">
|
||||||
|
<div i18n>{{ instanceName }} platform subscribes to content from <a routerLink="/about/follows" target="_blank">{{ totalFollowing }} other platforms</a>.</div>
|
||||||
|
|
||||||
|
<div i18n>Set your display preferences here.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" >
|
||||||
|
<label for="scope" i18n>Displayed videos</label>
|
||||||
|
|
||||||
|
<my-select-options inputId="scope" class="scope-select" formControlName="scope" [items]="availableScopes"></my-select-options>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-title" i18n>Content type</div>
|
<div class="section-title" i18n>Content type</div>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,13 @@ $filters-background: pvar(--bg-secondary-400);
|
||||||
margin-bottom: 45px;
|
margin-bottom: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-row {
|
.scope-row {
|
||||||
|
font-size: 20px;
|
||||||
|
color: pvar(--fg-350);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
@ -128,7 +134,7 @@ my-select-categories {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
@media screen and (max-width: $small-view) {
|
||||||
.first-row {
|
.filters-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { GlobalIconComponent, GlobalIconName } from '../shared-icons/global-icon
|
||||||
import { ButtonComponent } from '../shared-main/buttons/button.component'
|
import { ButtonComponent } from '../shared-main/buttons/button.component'
|
||||||
import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service'
|
import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service'
|
||||||
import { VideoFilterActive, VideoFilters } from './video-filters.model'
|
import { VideoFilterActive, VideoFilters } from './video-filters.model'
|
||||||
|
import { InstanceFollowService } from '../shared-instance/instance-follow.service'
|
||||||
|
|
||||||
const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
|
const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
|
||||||
|
|
||||||
|
@ -45,7 +46,8 @@ type QuickFilter = {
|
||||||
PeertubeCheckboxComponent,
|
PeertubeCheckboxComponent,
|
||||||
SelectOptionsComponent,
|
SelectOptionsComponent,
|
||||||
ButtonComponent
|
ButtonComponent
|
||||||
]
|
],
|
||||||
|
providers: [ InstanceFollowService ]
|
||||||
})
|
})
|
||||||
export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
||||||
@Input() filters: VideoFilters
|
@Input() filters: VideoFilters
|
||||||
|
@ -63,6 +65,9 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
quickFilters: QuickFilter[] = []
|
quickFilters: QuickFilter[] = []
|
||||||
|
|
||||||
|
instanceName: string
|
||||||
|
totalFollowing: number
|
||||||
|
|
||||||
private videoCategories: VideoConstant<number>[] = []
|
private videoCategories: VideoConstant<number>[] = []
|
||||||
private videoLanguages: VideoConstant<string>[] = []
|
private videoLanguages: VideoConstant<string>[] = []
|
||||||
|
|
||||||
|
@ -74,11 +79,15 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private modalService: PeertubeModalService,
|
private modalService: PeertubeModalService,
|
||||||
private redirectService: RedirectService,
|
private redirectService: RedirectService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private server: ServerService,
|
||||||
|
private followService: InstanceFollowService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
this.instanceName = this.server.getHTMLConfig().instance.name
|
||||||
|
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
sort: [ '' ],
|
sort: [ '' ],
|
||||||
nsfw: [ '' ],
|
nsfw: [ '' ],
|
||||||
|
@ -113,9 +122,12 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
||||||
this.serverService.getVideoLanguages()
|
this.serverService.getVideoLanguages()
|
||||||
.subscribe(languages => this.videoLanguages = languages)
|
.subscribe(languages => this.videoLanguages = languages)
|
||||||
|
|
||||||
|
this.followService.getFollowing({ pagination: { count: 1, start: 0 }, state: 'accepted' })
|
||||||
|
.subscribe(({ total }) => this.totalFollowing = total)
|
||||||
|
|
||||||
this.availableScopes = [
|
this.availableScopes = [
|
||||||
{ id: 'local', label: $localize`This platform only` },
|
{ id: 'local', label: $localize`Only videos from this platform` },
|
||||||
{ id: 'federated', label: $localize`All platforms` }
|
{ id: 'federated', label: $localize`Videos from all platforms` }
|
||||||
]
|
]
|
||||||
|
|
||||||
this.buildSortItems()
|
this.buildSortItems()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common'
|
import { NgClass, NgFor, NgIf } from '@angular/common'
|
||||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, booleanAttribute } from '@angular/core'
|
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, booleanAttribute } from '@angular/core'
|
||||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
AuthService,
|
AuthService,
|
||||||
ComponentPaginationLight,
|
ComponentPaginationLight,
|
||||||
|
@ -11,7 +11,6 @@ import {
|
||||||
UserService
|
UserService
|
||||||
} from '@app/core'
|
} from '@app/core'
|
||||||
import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
|
import { GlobalIconComponent, GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
|
||||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@peertube/peertube-core-utils'
|
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@peertube/peertube-core-utils'
|
||||||
import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models'
|
import { ResultList, UserRight, VideoSortField } from '@peertube/peertube-models'
|
||||||
import { logger } from '@root-helpers/logger'
|
import { logger } from '@root-helpers/logger'
|
||||||
|
@ -20,7 +19,6 @@ import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs
|
||||||
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
|
import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators'
|
||||||
import { ButtonComponent } from '../shared-main/buttons/button.component'
|
import { ButtonComponent } from '../shared-main/buttons/button.component'
|
||||||
import { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
|
import { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
|
||||||
import { FeedComponent } from '../shared-main/feeds/feed.component'
|
|
||||||
import { Syndication } from '../shared-main/feeds/syndication.model'
|
import { Syndication } from '../shared-main/feeds/syndication.model'
|
||||||
import { Video } from '../shared-main/video/video.model'
|
import { Video } from '../shared-main/video/video.model'
|
||||||
import { VideoFiltersHeaderComponent } from './video-filters-header.component'
|
import { VideoFiltersHeaderComponent } from './video-filters-header.component'
|
||||||
|
@ -52,14 +50,9 @@ enum GroupDate {
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
NgIf,
|
NgIf,
|
||||||
NgbTooltip,
|
|
||||||
NgClass,
|
NgClass,
|
||||||
FeedComponent,
|
|
||||||
NgFor,
|
NgFor,
|
||||||
RouterLinkActive,
|
|
||||||
RouterLink,
|
|
||||||
ButtonComponent,
|
ButtonComponent,
|
||||||
NgTemplateOutlet,
|
|
||||||
ButtonComponent,
|
ButtonComponent,
|
||||||
VideoFiltersHeaderComponent,
|
VideoFiltersHeaderComponent,
|
||||||
InfiniteScrollerDirective,
|
InfiniteScrollerDirective,
|
||||||
|
|
|
@ -223,37 +223,10 @@ my-video-thumbnail,
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $small-view) {
|
@media screen and (min-width: $small-view) {
|
||||||
:host-context(.expanded) {
|
@include more-dropdown-control();
|
||||||
@include more-dropdown-control();
|
@include edit-button-control();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
|
||||||
:host-context(.expanded) {
|
|
||||||
@include edit-button-control();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
@media screen and (max-width: $mobile-view) {
|
||||||
:host-context(.expanded) {
|
@include edit-button-in-mobile-view();
|
||||||
@include edit-button-in-mobile-view();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: #{$small-view + $menu-width}) {
|
|
||||||
:host-context(.main-col:not(.expanded)) {
|
|
||||||
@include more-dropdown-control();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: #{$small-view + $menu-width}) {
|
|
||||||
:host-context(.main-col:not(.expanded)) {
|
|
||||||
@include edit-button-control();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: #{$mobile-view + $menu-width}) {
|
|
||||||
:host-context(.main-col:not(.expanded)) {
|
|
||||||
@include edit-button-in-mobile-view();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
After Width: | Height: | Size: 327 B |
|
@ -11,6 +11,13 @@
|
||||||
@use './z-index';
|
@use './z-index';
|
||||||
@use './class-helpers/index.scss';
|
@use './class-helpers/index.scss';
|
||||||
|
|
||||||
|
@mixin main-col-expanded {
|
||||||
|
--main-col-width: 100vw;
|
||||||
|
|
||||||
|
width: calc(100% - #{$menu-collapsed-width});
|
||||||
|
margin-inline-start: $menu-collapsed-width;
|
||||||
|
}
|
||||||
|
|
||||||
/* clears the ‘X’ from Chrome */
|
/* clears the ‘X’ from Chrome */
|
||||||
input[type="search"]::-webkit-search-decoration,
|
input[type="search"]::-webkit-search-decoration,
|
||||||
input[type="search"]::-webkit-search-cancel-button,
|
input[type="search"]::-webkit-search-cancel-button,
|
||||||
|
@ -67,6 +74,14 @@ body {
|
||||||
--alert-primary-bg: #{pvar(--primary-200)};
|
--alert-primary-bg: #{pvar(--primary-200)};
|
||||||
--alert-primary-border-color: #{pvar(--primary-300)};
|
--alert-primary-border-color: #{pvar(--primary-300)};
|
||||||
|
|
||||||
|
--menu-margin-left: #{$menu-margin-left};
|
||||||
|
|
||||||
|
--header-height: #{$header-height};
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
--header-height: #{$header-height-mobile-view};
|
||||||
|
}
|
||||||
|
|
||||||
// Light theme
|
// Light theme
|
||||||
&[data-pt-theme=peertube-core-light],
|
&[data-pt-theme=peertube-core-light],
|
||||||
&[data-pt-theme=default] {
|
&[data-pt-theme=default] {
|
||||||
|
@ -104,8 +119,8 @@ body {
|
||||||
--bg: hsl(0 14% 7%);
|
--bg: hsl(0 14% 7%);
|
||||||
--bg-secondary: hsl(0 14% 22%);
|
--bg-secondary: hsl(0 14% 22%);
|
||||||
|
|
||||||
--alert-primary-fg: #{pvar(--on-primary)};
|
--alert-primary-fg: #{pvar(--primary-650)};
|
||||||
--alert-primary-bg: #{pvar(--primary-200)};
|
--alert-primary-bg: #{pvar(--primary-100)};
|
||||||
--alert-primary-border-color: #{pvar(--primary-300)};
|
--alert-primary-border-color: #{pvar(--primary-300)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +141,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
color: pvar(--bg);
|
color: pvar(--on-primary);
|
||||||
background-color: pvar(--primary-450);
|
background-color: pvar(--primary-450);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,17 +216,6 @@ code {
|
||||||
margin: 0 calc(#{pvar(--x-margin-content)} * -1);
|
margin: 0 calc(#{pvar(--x-margin-content)} * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-menu {
|
|
||||||
background-color: pvar(--bg-secondary-400);
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 pvar(--x-margin-content);
|
|
||||||
height: $sub-menu-height;
|
|
||||||
margin-bottom: $sub-menu-margin-bottom;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-to-content-sub-menu {
|
.skip-to-content-sub-menu {
|
||||||
display: block;
|
display: block;
|
||||||
z-index: z(modal);
|
z-index: z(modal);
|
||||||
|
@ -223,24 +227,21 @@ code {
|
||||||
|
|
||||||
// Override some properties if the main content is expanded (no menu on the left)
|
// Override some properties if the main content is expanded (no menu on the left)
|
||||||
&.expanded {
|
&.expanded {
|
||||||
--main-col-width: 100vw;
|
@include main-col-expanded();
|
||||||
|
|
||||||
width: calc(100% - #{$collapsed-menu-width});
|
|
||||||
|
|
||||||
@include margin-left($collapsed-menu-width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lock-scroll .main-row > router-outlet + * { /* stylelint-disable-line selector-max-compound-selectors */
|
&.lock-scroll .main-row > router-outlet + * { /* stylelint-disable-line selector-max-compound-selectors */
|
||||||
// Lock and hide body scrollbars
|
// Lock and hide body scrollbars
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
||||||
// Lock and hide sub-menu scrollbars
|
|
||||||
.sub-menu { /* stylelint-disable-line */
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-open,
|
||||||
|
.main-col.expanded,
|
||||||
|
.menu-open {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
my-global-icon[iconName=external-link] {
|
my-global-icon[iconName=external-link] {
|
||||||
margin: 0 0.3em;
|
margin: 0 0.3em;
|
||||||
width: 0.9em;
|
width: 0.9em;
|
||||||
|
@ -256,54 +257,49 @@ my-global-icon[iconName=external-link] {
|
||||||
/* the following applies from 500px to 900px and is partially overridden from 500px to 800px by changes below to $small-view */
|
/* the following applies from 500px to 900px and is partially overridden from 500px to 800px by changes below to $small-view */
|
||||||
.main-col,
|
.main-col,
|
||||||
.main-col.expanded {
|
.main-col.expanded {
|
||||||
.sub-menu {
|
.title-page {
|
||||||
padding: 0 50px;
|
font-size: 17px;
|
||||||
|
|
||||||
.title-page {
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $mobile-view) and (max-width: $small-view) {
|
@media screen and (max-width: $menu-overlay-view) {
|
||||||
.main-col {
|
.main-col {
|
||||||
width: 100%;
|
@include main-col-expanded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
@media screen and (max-width: $small-view) {
|
||||||
|
.main-col {
|
||||||
|
--x-margin-content: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-markdown-textarea {
|
||||||
|
.root {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text],
|
||||||
|
input[type=password],
|
||||||
|
input[type=email],
|
||||||
|
textarea,
|
||||||
|
.peertube-select-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption input[type=text] {
|
||||||
|
width: unset !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-view) {
|
||||||
.main-col,
|
.main-col,
|
||||||
.main-col.expanded {
|
.main-col.expanded {
|
||||||
--x-margin-content: 15px;
|
width: 100%;
|
||||||
|
|
||||||
@include margin-left(0);
|
@include margin-left(0);
|
||||||
|
|
||||||
.sub-menu {
|
|
||||||
width: 100vw;
|
|
||||||
padding: 0 15px;
|
|
||||||
margin-bottom: $sub-menu-margin-bottom-small-view;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
my-markdown-textarea {
|
|
||||||
.root {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text],
|
|
||||||
input[type=password],
|
|
||||||
input[type=email],
|
|
||||||
textarea,
|
|
||||||
.peertube-select-container {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption input[type=text] {
|
|
||||||
width: unset !important;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -199,30 +199,6 @@ body {
|
||||||
width: 100vw; // Make sure the content fits all the available width
|
width: 100vw; // Make sure the content fits all the available width
|
||||||
}
|
}
|
||||||
|
|
||||||
// On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll
|
|
||||||
@media (hover: none) and (pointer: coarse) {
|
|
||||||
.modal-open,
|
|
||||||
.menu-open {
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On touchscreen devices display content overlay when opened menu
|
|
||||||
.menu-open {
|
|
||||||
.main-col {
|
|
||||||
&::before {
|
|
||||||
background-color: #000;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
opacity: 0.75;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
z-index: z(overlay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Nav
|
// Nav
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
|
|
||||||
.anchor {
|
.anchor {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: #{- ($header-height + 20px)};
|
top: -calc(#{pvar(--header-height)} + 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -97,7 +97,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-button {
|
.secondary-button {
|
||||||
@include secondary-button($fg: pvar(--alert-primary-fg));
|
@include secondary-button(
|
||||||
|
$fg: pvar(--alert-primary-fg),
|
||||||
|
$active-bg: rgba(0, 0, 0, 0.4),
|
||||||
|
$hover-bg: rgba(0, 0, 0, 0.3),
|
||||||
|
$border-color: pvar(--alert-primary-fg)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
|
|
||||||
.banner {
|
.banner {
|
||||||
@include block-ratio('img', $banner-inverted-ratio);
|
@include block-ratio('img', $banner-inverted-ratio);
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.revert-margin-content.banner {
|
.revert-margin-content.banner {
|
||||||
|
|
|
@ -47,3 +47,15 @@
|
||||||
.min-width-0 {
|
.min-width-0 {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.d-none-mw {
|
||||||
|
@include on-mobile-main-col {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-none-sw {
|
||||||
|
@include on-small-main-col {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.badge-primary {
|
&.badge-primary {
|
||||||
color: pvar(--bg);
|
color: pvar(--on-primary);
|
||||||
background-color: pvar(--primary);
|
background-color: pvar(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ $input-focus-bg: pvar(--input-bg);
|
||||||
$input-btn-focus-width: 0;
|
$input-btn-focus-width: 0;
|
||||||
$input-btn-focus-color: inherit;
|
$input-btn-focus-color: inherit;
|
||||||
$input-focus-border-color: pvar(--input-border-color);
|
$input-focus-border-color: pvar(--input-border-color);
|
||||||
$input-focus-box-shadow: 0 0 0 0.25rem pvar(--primary-100);
|
$input-focus-box-shadow: #{$focus-box-shadow-form};
|
||||||
|
|
||||||
$input-group-addon-color: pvar(--fg);
|
$input-group-addon-color: pvar(--fg);
|
||||||
$input-group-addon-bg: pvar(--bg-secondary-500);
|
$input-group-addon-bg: pvar(--bg-secondary-500);
|
||||||
|
@ -64,7 +64,7 @@ $dropdown-bg: pvar(--bg);
|
||||||
$accordion-button-active-bg: pvar(--primary-50);
|
$accordion-button-active-bg: pvar(--primary-50);
|
||||||
$accordion-button-active-color: pvar(--fg);
|
$accordion-button-active-color: pvar(--fg);
|
||||||
$accordion-button-focus-border-color: pvar(--primary-100);
|
$accordion-button-focus-border-color: pvar(--primary-100);
|
||||||
$accordion-button-focus-box-shadow: 0 0 0 .15rem pvar(--primary-100);
|
$accordion-button-focus-box-shadow: #{$focus-box-shadow-form};
|
||||||
$accordion-border-color: pvar(--input-border-color);
|
$accordion-border-color: pvar(--input-border-color);
|
||||||
|
|
||||||
$card-color: pvar(--fg);
|
$card-color: pvar(--fg);
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
|
||||||
@mixin secondary-button ($fg: pvar(--fg), $active-bg: pvar(--bg-secondary-400), $hover-bg: pvar(--bg-secondary-450)) {
|
@mixin secondary-button (
|
||||||
|
$fg: pvar(--fg),
|
||||||
|
$active-bg: pvar(--bg-secondary-500),
|
||||||
|
$hover-bg: pvar(--bg-secondary-450),
|
||||||
|
$border-color: pvar(--bg-secondary-500)
|
||||||
|
) {
|
||||||
& {
|
& {
|
||||||
color: $fg;
|
color: $fg;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid pvar(--bg-secondary-500) !important;
|
border: 1px solid $border-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
|
@ -16,7 +21,7 @@
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
color: $fg;
|
color: $fg;
|
||||||
background-color: $active-bg;
|
background-color: $active-bg;
|
||||||
border-color: pvar(--bg-secondary-500);
|
border-color: $border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override bootstrap
|
// Override bootstrap
|
||||||
|
@ -25,7 +30,7 @@
|
||||||
&.btn.show {
|
&.btn.show {
|
||||||
color: $fg !important;
|
color: $fg !important;
|
||||||
background-color: $active-bg !important;
|
background-color: $active-bg !important;
|
||||||
border-color: pvar(--bg-secondary-500) !important;
|
border-color: $border-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -215,7 +220,7 @@
|
||||||
@mixin button-focus($color) {
|
@mixin button-focus($color) {
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
box-shadow: #{$focus-box-shadow-form} $color;
|
box-shadow: #{$focus-box-shadow-dimensions} $color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,12 @@
|
||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
box-shadow: $focus-box-shadow-form;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
border-color: pvar(--fg);
|
}
|
||||||
box-shadow: none;
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: calc(#{$width} + 40px)) {
|
@media screen and (max-width: calc(#{$width} + 40px)) {
|
||||||
|
@ -95,21 +98,29 @@
|
||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: 0;
|
box-shadow: $focus-box-shadow-form;
|
||||||
border-color: pvar(--fg);
|
}
|
||||||
box-shadow: none;
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thanks: https://codepen.io/manabox/pen/raQmpL
|
// Thanks: https://codepen.io/manabox/pen/raQmpL
|
||||||
@mixin peertube-radio-container {
|
@mixin peertube-radio-container {
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: $form-input-font-size;
|
font-size: $form-input-font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
[type=radio]:focus-visible + label::before {
|
[type=radio]:focus-visible,[type=radio]:focus {
|
||||||
outline: 2px solid;
|
& + label::before {
|
||||||
|
box-shadow: $focus-box-shadow-form;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[type=radio]:checked,
|
[type=radio]:checked,
|
||||||
|
@ -129,6 +140,7 @@
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: $font-regular;
|
font-weight: $font-regular;
|
||||||
|
color: pvar(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[type=radio]:checked + label::before,
|
[type=radio]:checked + label::before,
|
||||||
|
@ -146,12 +158,12 @@
|
||||||
[type=radio]:checked + label::after,
|
[type=radio]:checked + label::after,
|
||||||
[type=radio]:not(:checked) + label::after {
|
[type=radio]:not(:checked) + label::after {
|
||||||
content: '';
|
content: '';
|
||||||
width: 12px;
|
width: 8px;
|
||||||
height: 12px;
|
height: 8px;
|
||||||
background: pvar(--border-primary);
|
background: pvar(--primary);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3px;
|
top: 5px;
|
||||||
left: 3px;
|
left: 5px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +173,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[type=radio]:checked + label::before {
|
[type=radio]:checked + label::before {
|
||||||
border: 4px solid pvar(--fg-400);
|
border: 2px solid pvar(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[type=radio]:checked + label::after {
|
[type=radio]:checked + label::after {
|
||||||
|
@ -184,7 +196,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
&:focus + span {
|
&:focus + span {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ span {
|
+ span {
|
||||||
|
@ -200,10 +212,10 @@
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 3px;
|
||||||
left: 5px;
|
left: 6px;
|
||||||
width: 6px;
|
width: 5px;
|
||||||
height: 12px;
|
height: 8px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: rotate(45deg) scale(0);
|
transform: rotate(45deg) scale(0);
|
||||||
border-right: 2px solid pvar(--on-primary);
|
border-right: 2px solid pvar(--on-primary);
|
||||||
|
@ -212,10 +224,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked + span {
|
&:checked + span {
|
||||||
border-color: transparent;
|
border-color: pvar(--fg);
|
||||||
background: pvar(--border-primary);
|
background: pvar(--fg);
|
||||||
animation: jelly 0.6s ease;
|
animation: jelly 0.6s ease;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: pvar(--primary);
|
||||||
|
position: absolute;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: 2px solid pvar(--on-primary);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: rotate(45deg) scale(1);
|
transform: rotate(45deg) scale(1);
|
||||||
|
@ -232,7 +255,7 @@
|
||||||
|
|
||||||
&[disabled] + span,
|
&[disabled] + span,
|
||||||
&[disabled] + span + span {
|
&[disabled] + span + span {
|
||||||
opacity: 0.5;
|
opacity: 0.4;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,30 +240,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin on-small-main-col () {
|
@mixin on-small-main-col () {
|
||||||
:host-context(.main-col:not(.expanded)) {
|
@media screen and (max-width: $small-view) {
|
||||||
@media screen and (max-width: #{$small-view + $menu-width}) {
|
@content;
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(.main-col.expanded) {
|
|
||||||
@media screen and (max-width: $small-view) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin on-mobile-main-col () {
|
@mixin on-mobile-main-col () {
|
||||||
:host-context(.main-col:not(.expanded)) {
|
@media screen and (max-width: $mobile-view) {
|
||||||
@media screen and (max-width: #{$mobile-view + $menu-width}) {
|
@content;
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(.main-col.expanded) {
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,12 @@ $white: #fff;
|
||||||
$button-font-size: 1rem;
|
$button-font-size: 1rem;
|
||||||
|
|
||||||
$header-height: 94px;
|
$header-height: 94px;
|
||||||
|
$header-height-mobile-view: 144px;
|
||||||
|
|
||||||
$menu-width: 248px;
|
$menu-width: 248px;
|
||||||
$collapsed-menu-width: 48px;
|
$menu-collapsed-width: 50px;
|
||||||
|
$menu-margin-left: 2rem;
|
||||||
|
$menu-overlay-view: 1200px;
|
||||||
|
|
||||||
$sub-menu-height: 81px;
|
$sub-menu-height: 81px;
|
||||||
|
|
||||||
|
@ -53,7 +56,8 @@ $player-portrait-bottom-space: 50px;
|
||||||
$sub-menu-margin-bottom: 30px;
|
$sub-menu-margin-bottom: 30px;
|
||||||
$sub-menu-margin-bottom-small-view: 10px;
|
$sub-menu-margin-bottom-small-view: 10px;
|
||||||
|
|
||||||
$focus-box-shadow-form: 0 0 0 .2rem;
|
$focus-box-shadow-dimensions: 0 0 0 .2rem;
|
||||||
|
|
||||||
$form-input-font-size: 16px;
|
$form-input-font-size: 16px;
|
||||||
|
|
||||||
$video-watch-info-margin-left: 44px;
|
$video-watch-info-margin-left: 44px;
|
||||||
|
@ -71,6 +75,7 @@ $variables: (
|
||||||
--x-margin-content: var(--x-margin-content),
|
--x-margin-content: var(--x-margin-content),
|
||||||
--x-videos-margin-content: var(--x-videos-margin-content),
|
--x-videos-margin-content: var(--x-videos-margin-content),
|
||||||
--main-col-width: var(--main-col-width),
|
--main-col-width: var(--main-col-width),
|
||||||
|
--header-height: var(--header-height),
|
||||||
|
|
||||||
--fg: var(--fg),
|
--fg: var(--fg),
|
||||||
--bg: var(--bg),
|
--bg: var(--bg),
|
||||||
|
@ -121,6 +126,8 @@ $variables: (
|
||||||
--on-primary: var(--on-primary),
|
--on-primary: var(--on-primary),
|
||||||
|
|
||||||
--primary: var(--primary),
|
--primary: var(--primary),
|
||||||
|
--primary-700: var(--primary-700),
|
||||||
|
--primary-650: var(--primary-650),
|
||||||
--primary-600: var(--primary-600),
|
--primary-600: var(--primary-600),
|
||||||
--primary-550: var(--primary-550),
|
--primary-550: var(--primary-550),
|
||||||
--primary-500: var(--primary-500),
|
--primary-500: var(--primary-500),
|
||||||
|
@ -140,6 +147,8 @@ $variables: (
|
||||||
--alert-primary-bg: var(--alert-primary-bg),
|
--alert-primary-bg: var(--alert-primary-bg),
|
||||||
--alert-primary-border-color: var(--alert-primary-border-color),
|
--alert-primary-border-color: var(--alert-primary-border-color),
|
||||||
|
|
||||||
|
--menu-margin-left: var(--menu-margin-left),
|
||||||
|
|
||||||
// Optional variables
|
// Optional variables
|
||||||
--menu-fg: var(--menu-fg),
|
--menu-fg: var(--menu-fg),
|
||||||
--menu-bg: var(--menu-bg)
|
--menu-bg: var(--menu-bg)
|
||||||
|
@ -168,7 +177,6 @@ $variables: (
|
||||||
|
|
||||||
$zindex: (
|
$zindex: (
|
||||||
miniature : 10,
|
miniature : 10,
|
||||||
sub-menu : 12500,
|
|
||||||
overlay : 12550,
|
overlay : 12550,
|
||||||
menu : 12600,
|
menu : 12600,
|
||||||
search-typeahead: 12650,
|
search-typeahead: 12650,
|
||||||
|
@ -193,3 +201,4 @@ $zindex: (
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
$separator-border-color: pvar(--bg-secondary-400);
|
$separator-border-color: pvar(--bg-secondary-400);
|
||||||
|
$focus-box-shadow-form: #{$focus-box-shadow-dimensions} #{pvar(--fg-100)};
|
||||||
|
|
|
@ -80,7 +80,7 @@ body .p-paginator .p-paginator-next:focus,
|
||||||
body .p-paginator .p-paginator-last:focus {
|
body .p-paginator .p-paginator-last:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
body .p-paginator .p-paginator-current {
|
body .p-paginator .p-paginator-current {
|
||||||
color: pvar(--fg);
|
color: pvar(--fg);
|
||||||
|
@ -134,7 +134,7 @@ body .p-paginator .p-paginator-pages .p-paginator-page:not(.p-highlight):hover {
|
||||||
body .p-paginator .p-paginator-pages .p-paginator-page:focus {
|
body .p-paginator .p-paginator-pages .p-paginator-page:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
body .p-paginator .p-dropdown {
|
body .p-paginator .p-dropdown {
|
||||||
@include margin-left(0.5em);
|
@include margin-left(0.5em);
|
||||||
|
@ -157,7 +157,7 @@ body .p-dropdown:not(.p-disabled):hover {
|
||||||
body .p-dropdown:not(.p-disabled).p-focus {
|
body .p-dropdown:not(.p-disabled).p-focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
border-color: pvar(--input-border-color);
|
border-color: pvar(--input-border-color);
|
||||||
}
|
}
|
||||||
body .p-dropdown.p-dropdown-clearable .p-dropdown-label {
|
body .p-dropdown.p-dropdown-clearable .p-dropdown-label {
|
||||||
|
@ -281,7 +281,7 @@ body .p-datepicker:not(.p-disabled) .p-datepicker-header .p-datepicker-prev:focu
|
||||||
body .p-datepicker:not(.p-disabled) .p-datepicker-header .p-datepicker-next:focus {
|
body .p-datepicker:not(.p-disabled) .p-datepicker-header .p-datepicker-next:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
body .p-datepicker .p-datepicker-header {
|
body .p-datepicker .p-datepicker-header {
|
||||||
padding: 0.429em 0.857em 0.429em 0.857em;
|
padding: 0.429em 0.857em 0.429em 0.857em;
|
||||||
|
@ -311,7 +311,7 @@ body .p-datepicker .p-datepicker-header .p-datepicker-title select {
|
||||||
body .p-datepicker .p-datepicker-header .p-datepicker-title select:focus {
|
body .p-datepicker .p-datepicker-header .p-datepicker-title select:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
body .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
|
body .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
|
||||||
@include margin-right(0.5rem);
|
@include margin-right(0.5rem);
|
||||||
|
@ -349,7 +349,7 @@ body .p-datepicker table td > a {
|
||||||
body .p-datepicker table td > a:focus {
|
body .p-datepicker table td > a:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2em pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
body .p-datepicker table td.p-datepicker-today > a,
|
body .p-datepicker table td.p-datepicker-today > a,
|
||||||
body .p-datepicker table td.p-datepicker-today > span {
|
body .p-datepicker table td.p-datepicker-today > span {
|
||||||
|
@ -478,7 +478,7 @@ body p-autocomplete.ng-dirty.ng-invalid > .p-autocomplete > .p-inputtext {
|
||||||
.p-chips:not(.p-disabled).p-focus .p-chips-multiple-container {
|
.p-chips:not(.p-disabled).p-focus .p-chips-multiple-container {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.25rem pvar(--primary-300);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
.p-chips .p-chips-multiple-container {
|
.p-chips .p-chips-multiple-container {
|
||||||
padding: pvar(--input-y-padding) pvar(--input-x-padding);
|
padding: pvar(--input-y-padding) pvar(--input-x-padding);
|
||||||
|
@ -534,7 +534,7 @@ p-chips.p-chips-clearable .p-chips-clear-icon {
|
||||||
.p-multiselect:not(.p-disabled).p-focus {
|
.p-multiselect:not(.p-disabled).p-focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.25rem pvar(--primary-300);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
.p-multiselect .p-multiselect-label {
|
.p-multiselect .p-multiselect-label {
|
||||||
padding: pvar(--input-y-padding) pvar(--input-x-padding);
|
padding: pvar(--input-y-padding) pvar(--input-x-padding);
|
||||||
|
@ -620,14 +620,14 @@ p-chips.p-chips-clearable .p-chips-clear-icon {
|
||||||
transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
|
transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:enabled:hover {
|
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:enabled:hover {
|
||||||
color: pvar(--primary);
|
color: pvar(--fg-400);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:focus-visible {
|
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:focus-visible {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.2rem pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
.p-multiselect-panel .p-multiselect-items {
|
.p-multiselect-panel .p-multiselect-items {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -708,7 +708,7 @@ p-multiselect.ng-dirty.ng-invalid > .p-multiselect {
|
||||||
.p-inputtext:enabled:focus {
|
.p-inputtext:enabled:focus {
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
box-shadow: 0 0 0 0.25rem pvar(--primary-300);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
.p-inputtext.ng-dirty.ng-invalid {
|
.p-inputtext.ng-dirty.ng-invalid {
|
||||||
border-color: pvar(--red);
|
border-color: pvar(--red);
|
||||||
|
@ -924,7 +924,7 @@ p-table {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.p-focus {
|
&.p-focus {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-label {
|
.p-label {
|
||||||
|
@ -962,7 +962,7 @@ p-table {
|
||||||
|
|
||||||
&.focus-within,
|
&.focus-within,
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100);
|
box-shadow: $focus-box-shadow-form;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.p-disabled:hover {
|
&.p-disabled:hover {
|
||||||
|
@ -998,7 +998,7 @@ p-table {
|
||||||
|
|
||||||
&.focus-within,
|
&.focus-within,
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: #{$focus-box-shadow-form} pvar(--primary-100) !important;
|
box-shadow: #{$focus-box-shadow-form} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.p-highlight {
|
&.p-highlight {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { OAuthUserTokens, objectToUrlEncoded } from '../../../root-helpers'
|
||||||
import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage'
|
import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage'
|
||||||
|
|
||||||
export class AuthHTTP {
|
export class AuthHTTP {
|
||||||
private readonly LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
|
private readonly LS_OAUTH_CLIENT_KEYS = {
|
||||||
CLIENT_ID: 'client_id',
|
CLIENT_ID: 'client_id',
|
||||||
CLIENT_SECRET: 'client_secret'
|
CLIENT_SECRET: 'client_secret'
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ export class AuthHTTP {
|
||||||
if (res.status !== HttpStatusCode.UNAUTHORIZED_401) return res
|
if (res.status !== HttpStatusCode.UNAUTHORIZED_401) return res
|
||||||
|
|
||||||
const refreshingTokenPromise = new Promise<void>((resolve, reject) => {
|
const refreshingTokenPromise = new Promise<void>((resolve, reject) => {
|
||||||
const clientId: string = peertubeLocalStorage.getItem(this.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
|
const clientId: string = peertubeLocalStorage.getItem(this.LS_OAUTH_CLIENT_KEYS.CLIENT_ID)
|
||||||
const clientSecret: string = peertubeLocalStorage.getItem(this.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
|
const clientSecret: string = peertubeLocalStorage.getItem(this.LS_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
|
||||||
|
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
headers.set('Content-Type', 'application/x-www-form-urlencoded')
|
headers.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
|
|
|
@ -938,7 +938,7 @@ instance:
|
||||||
# - 17 # Kids
|
# - 17 # Kids
|
||||||
# - 18 # Food
|
# - 18 # Food
|
||||||
|
|
||||||
default_client_route: '/videos/trending'
|
default_client_route: '/videos/browse'
|
||||||
|
|
||||||
# Whether or not the instance is dedicated to NSFW content
|
# Whether or not the instance is dedicated to NSFW content
|
||||||
# Enabling it will allow other administrators to know that you are mainly federating sensitive content
|
# Enabling it will allow other administrators to know that you are mainly federating sensitive content
|
||||||
|
|
Loading…
Reference in New Issue