mirror of https://github.com/Chocobozzz/PeerTube
Videos horizontal menu
parent
e162a9a117
commit
55b2dc86e1
|
@ -1,11 +1,10 @@
|
||||||
|
<h1 class="visually-hidden" i18n>Videos</h1>
|
||||||
|
|
||||||
<my-videos-list
|
<my-videos-list
|
||||||
#videosList
|
#videosList
|
||||||
|
|
||||||
*ngIf="account"
|
*ngIf="account"
|
||||||
|
|
||||||
[title]="title"
|
|
||||||
displayTitle="false"
|
|
||||||
|
|
||||||
[getVideosObservableFunction]="getVideosObservableFunction"
|
[getVideosObservableFunction]="getVideosObservableFunction"
|
||||||
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ export class AccountVideosComponent implements OnInit, OnDestroy, DisableForReus
|
||||||
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
||||||
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
||||||
|
|
||||||
title = $localize`Videos`
|
|
||||||
defaultSort = '-publishedAt' as VideoSortField
|
defaultSort = '-publishedAt' as VideoSortField
|
||||||
|
|
||||||
account: Account
|
account: Account
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
||||||
import { CustomMarkupContainerComponent } from '../shared/shared-custom-markup/custom-markup-container.component'
|
|
||||||
import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
|
import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
|
||||||
|
import { CustomMarkupContainerComponent } from '../shared/shared-custom-markup/custom-markup-container.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
<h1 class="visually-hidden" i18n>Videos</h1>
|
||||||
|
|
||||||
<my-videos-list
|
<my-videos-list
|
||||||
#videosList
|
#videosList
|
||||||
|
|
||||||
*ngIf="videoChannel"
|
*ngIf="videoChannel"
|
||||||
|
|
||||||
[title]="title"
|
|
||||||
displayTitle="false"
|
|
||||||
|
|
||||||
[getVideosObservableFunction]="getVideosObservableFunction"
|
[getVideosObservableFunction]="getVideosObservableFunction"
|
||||||
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ export class VideoChannelVideosComponent implements OnInit, AfterViewInit, OnDes
|
||||||
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
||||||
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
||||||
|
|
||||||
title = $localize`Videos`
|
|
||||||
defaultSort = '-publishedAt' as VideoSortField
|
defaultSort = '-publishedAt' as VideoSortField
|
||||||
|
|
||||||
displayOptions: MiniatureDisplayOptions = {
|
displayOptions: MiniatureDisplayOptions = {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { UrlSegment } from '@angular/router'
|
import { inject } from '@angular/core'
|
||||||
import { LoginGuard } from '@app/core'
|
import { Routes } from '@angular/router'
|
||||||
import { OverviewService, VideosListCommonPageComponent } from './video-list'
|
import { LoginGuard, RedirectService } from '@app/core'
|
||||||
import { VideoOverviewComponent } from './video-list/overview/video-overview.component'
|
import { AbuseService } from '@app/shared/shared-moderation/abuse.service'
|
||||||
import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component'
|
|
||||||
import { BlocklistService } from '@app/shared/shared-moderation/blocklist.service'
|
import { BlocklistService } from '@app/shared/shared-moderation/blocklist.service'
|
||||||
import { VideoBlockService } from '@app/shared/shared-moderation/video-block.service'
|
import { VideoBlockService } from '@app/shared/shared-moderation/video-block.service'
|
||||||
import { UserSubscriptionService } from '@app/shared/shared-user-subscription/user-subscription.service'
|
import { UserSubscriptionService } from '@app/shared/shared-user-subscription/user-subscription.service'
|
||||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
||||||
import { AbuseService } from '@app/shared/shared-moderation/abuse.service'
|
import { OverviewService, VideosListAllComponent } from './video-list'
|
||||||
|
import { VideoOverviewComponent } from './video-list/overview/video-overview.component'
|
||||||
|
import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
|
@ -31,26 +32,36 @@ export default [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Old URL redirections
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
{
|
{
|
||||||
// Old URL redirection
|
|
||||||
path: 'most-liked',
|
path: 'most-liked',
|
||||||
redirectTo: 'trending?sort=most-liked'
|
redirectTo: 'browse?scope=federated&sort=-likes'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
matcher: (url: UrlSegment[]) => {
|
path: 'trending',
|
||||||
if (url.length === 1 && [ 'recently-added', 'trending', 'local' ].includes(url[0].path)) {
|
redirectTo: () => {
|
||||||
return {
|
const redirectService = inject(RedirectService)
|
||||||
consumed: url,
|
|
||||||
posParams: {
|
|
||||||
page: new UrlSegment(url[0].path, {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return 'browse?scope=federated&sort=-' + redirectService.getDefaultTrendingAlgorithm()
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'recently-added',
|
||||||
|
redirectTo: 'browse?scope=federated&sort=-publishedAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'local',
|
||||||
|
redirectTo: 'browse?scope=local&sort=-publishedAt'
|
||||||
|
},
|
||||||
|
|
||||||
component: VideosListCommonPageComponent,
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'browse',
|
||||||
|
|
||||||
|
component: VideosListAllComponent,
|
||||||
data: {
|
data: {
|
||||||
reuse: {
|
reuse: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -75,4 +86,4 @@ export default [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
] satisfies Routes
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './overview'
|
export * from './overview'
|
||||||
export * from './videos-list-common-page.component'
|
export * from './videos-list-all.component'
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
<h1 class="visually-hidden" i18n>Videos from your subscriptions</h1>
|
||||||
|
|
||||||
<my-videos-list
|
<my-videos-list
|
||||||
[getVideosObservableFunction]="getVideosObservableFunction"
|
[getVideosObservableFunction]="getVideosObservableFunction"
|
||||||
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
||||||
|
|
||||||
[title]="titlePage"
|
|
||||||
|
|
||||||
[defaultSort]="defaultSort"
|
[defaultSort]="defaultSort"
|
||||||
|
|
||||||
displayFilters="false"
|
displayFilters="false"
|
||||||
|
|
|
@ -29,8 +29,6 @@ export class VideoUserSubscriptionsComponent implements DisableForReuseHook {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
titlePage = $localize`Videos from your subscriptions`
|
|
||||||
|
|
||||||
disabled = false
|
disabled = false
|
||||||
|
|
||||||
private feedToken: string
|
private feedToken: string
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
<h1 class="visually-hidden" i18n>Browse videos</h1>
|
||||||
|
|
||||||
<my-videos-list
|
<my-videos-list
|
||||||
[getVideosObservableFunction]="getVideosObservableFunction"
|
[getVideosObservableFunction]="getVideosObservableFunction"
|
||||||
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
[getSyndicationItemsFunction]="getSyndicationItemsFunction"
|
||||||
[baseRouteBuilderFunction]="baseRouteBuilderFunction"
|
|
||||||
|
|
||||||
[title]="title"
|
|
||||||
[titleTooltip]="titleTooltip"
|
|
||||||
|
|
||||||
[defaultSort]="defaultSort"
|
[defaultSort]="defaultSort"
|
||||||
[defaultScope]="defaultScope"
|
[defaultScope]="defaultScope"
|
|
@ -1,30 +1,23 @@
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ComponentPaginationLight, DisableForReuseHook, MetaService, RedirectService, ServerService } from '@app/core'
|
import { ComponentPaginationLight, DisableForReuseHook, MetaService, ServerService } from '@app/core'
|
||||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
||||||
import { VideoFilterScope, VideoFilters } from '@app/shared/shared-video-miniature/video-filters.model'
|
import { VideoFilterScope, VideoFilters } from '@app/shared/shared-video-miniature/video-filters.model'
|
||||||
import { ClientFilterHookName, VideoSortField } from '@peertube/peertube-models'
|
import { VideoSortField } from '@peertube/peertube-models'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { VideosListComponent } from '../../shared/shared-video-miniature/videos-list.component'
|
import { VideosListComponent } from '../../shared/shared-video-miniature/videos-list.component'
|
||||||
|
|
||||||
export type VideosListCommonPageRouteData = {
|
|
||||||
sort: VideoSortField
|
|
||||||
|
|
||||||
scope: VideoFilterScope
|
|
||||||
hookParams: ClientFilterHookName
|
|
||||||
hookResult: ClientFilterHookName
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './videos-list-common-page.component.html',
|
templateUrl: './videos-list-all.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ VideosListComponent ]
|
imports: [
|
||||||
|
VideosListComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class VideosListCommonPageComponent implements OnInit, OnDestroy, DisableForReuseHook {
|
export class VideosListAllComponent implements OnInit, OnDestroy, DisableForReuseHook {
|
||||||
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
||||||
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
|
||||||
baseRouteBuilderFunction = this.baseRouteBuilder.bind(this)
|
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
titleTooltip: string
|
titleTooltip: string
|
||||||
|
@ -34,16 +27,12 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
defaultSort: VideoSortField
|
defaultSort: VideoSortField
|
||||||
defaultScope: VideoFilterScope
|
defaultScope: VideoFilterScope
|
||||||
|
|
||||||
hookParams: ClientFilterHookName
|
|
||||||
hookResult: ClientFilterHookName
|
|
||||||
|
|
||||||
loadUserVideoPreferences = true
|
loadUserVideoPreferences = true
|
||||||
|
|
||||||
displayFilters = true
|
displayFilters = true
|
||||||
|
|
||||||
disabled = false
|
disabled = false
|
||||||
|
|
||||||
private trendingDays: number
|
|
||||||
private routeSub: Subscription
|
private routeSub: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
@ -51,17 +40,15 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private videoService: VideoService,
|
private videoService: VideoService,
|
||||||
private hooks: HooksService,
|
private hooks: HooksService,
|
||||||
private meta: MetaService,
|
private meta: MetaService
|
||||||
private redirectService: RedirectService
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.trendingDays = this.server.getHTMLConfig().trending.videos.intervalDays
|
this.defaultSort = '-publishedAt'
|
||||||
|
this.defaultScope = 'federated'
|
||||||
|
|
||||||
this.routeSub = this.route.params.subscribe(params => {
|
this.routeSub = this.route.params.subscribe(() => this.update())
|
||||||
this.update(params['page'])
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
@ -80,8 +67,8 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
this.videoService.getVideos.bind(this.videoService),
|
this.videoService.getVideos.bind(this.videoService),
|
||||||
params,
|
params,
|
||||||
'common',
|
'common',
|
||||||
this.hookParams,
|
'filter:api.browse-videos.videos.list.params',
|
||||||
this.hookResult
|
'filter:api.browse-videos.videos.list.result'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,18 +83,6 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
this.updateGroupByDate(filters.sort)
|
this.updateGroupByDate(filters.sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseRouteBuilder (filters: VideoFilters) {
|
|
||||||
const sanitizedSort = this.getSanitizedSort(filters.sort)
|
|
||||||
|
|
||||||
let suffix: string
|
|
||||||
|
|
||||||
if (filters.scope === 'local') suffix = 'local'
|
|
||||||
else if (sanitizedSort === 'publishedAt') suffix = 'recently-added'
|
|
||||||
else suffix = 'trending'
|
|
||||||
|
|
||||||
return [ '/videos', suffix ]
|
|
||||||
}
|
|
||||||
|
|
||||||
disableForReuse () {
|
disableForReuse () {
|
||||||
this.disabled = true
|
this.disabled = true
|
||||||
}
|
}
|
||||||
|
@ -116,80 +91,19 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
this.disabled = false
|
this.disabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
update (page: string) {
|
update () {
|
||||||
const data = this.getData(page)
|
|
||||||
|
|
||||||
this.hookParams = data.hookParams
|
|
||||||
this.hookResult = data.hookResult
|
|
||||||
|
|
||||||
this.defaultSort = data.sort
|
|
||||||
this.defaultScope = data.scope
|
|
||||||
|
|
||||||
this.buildTitle()
|
this.buildTitle()
|
||||||
this.updateGroupByDate(this.defaultSort)
|
this.updateGroupByDate(this.defaultSort)
|
||||||
|
|
||||||
this.meta.setTitle(this.title)
|
this.meta.setTitle(this.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getData (page: string) {
|
|
||||||
if (page === 'trending') return this.generateTrendingData(this.route.snapshot)
|
|
||||||
|
|
||||||
if (page === 'local') return this.generateLocalData()
|
|
||||||
|
|
||||||
return this.generateRecentlyAddedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateRecentlyAddedData (): VideosListCommonPageRouteData {
|
|
||||||
return {
|
|
||||||
sort: '-publishedAt',
|
|
||||||
scope: 'federated',
|
|
||||||
hookParams: 'filter:api.recently-added-videos.videos.list.params',
|
|
||||||
hookResult: 'filter:api.recently-added-videos.videos.list.result'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLocalData (): VideosListCommonPageRouteData {
|
|
||||||
return {
|
|
||||||
sort: '-publishedAt' as VideoSortField,
|
|
||||||
scope: 'local',
|
|
||||||
hookParams: 'filter:api.local-videos.videos.list.params',
|
|
||||||
hookResult: 'filter:api.local-videos.videos.list.result'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateTrendingData (route: ActivatedRouteSnapshot): VideosListCommonPageRouteData {
|
|
||||||
const sort = route.queryParams['sort'] ?? this.parseTrendingAlgorithm(this.redirectService.getDefaultTrendingAlgorithm())
|
|
||||||
|
|
||||||
return {
|
|
||||||
sort,
|
|
||||||
scope: 'federated',
|
|
||||||
hookParams: 'filter:api.trending-videos.videos.list.params',
|
|
||||||
hookResult: 'filter:api.trending-videos.videos.list.result'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseTrendingAlgorithm (algorithm: string): VideoSortField {
|
|
||||||
switch (algorithm) {
|
|
||||||
case 'most-viewed':
|
|
||||||
return '-trending'
|
|
||||||
|
|
||||||
case 'most-liked':
|
|
||||||
return '-likes'
|
|
||||||
|
|
||||||
// We'll automatically apply "best" sort if using "hot" sort with a logged user
|
|
||||||
case 'best':
|
|
||||||
return '-hot'
|
|
||||||
|
|
||||||
default:
|
|
||||||
return '-' + algorithm as VideoSortField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateGroupByDate (sort: VideoSortField) {
|
private updateGroupByDate (sort: VideoSortField) {
|
||||||
this.groupByDate = sort === '-publishedAt' || sort === 'publishedAt'
|
this.groupByDate = sort === '-publishedAt' || sort === 'publishedAt'
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildTitle (scope: VideoFilterScope = this.defaultScope, sort: VideoSortField = this.defaultSort) {
|
private buildTitle (scope: VideoFilterScope = this.defaultScope, sort: VideoSortField = this.defaultSort) {
|
||||||
|
const trendingDays = this.server.getHTMLConfig().trending.videos.intervalDays
|
||||||
const sanitizedSort = this.getSanitizedSort(sort)
|
const sanitizedSort = this.getSanitizedSort(sort)
|
||||||
|
|
||||||
if (scope === 'local') {
|
if (scope === 'local') {
|
||||||
|
@ -223,12 +137,12 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sanitizedSort === 'trending') {
|
if (sanitizedSort === 'trending') {
|
||||||
if (this.trendingDays === 1) {
|
if (trendingDays === 1) {
|
||||||
this.titleTooltip = $localize`Videos with the most views during the last 24 hours`
|
this.titleTooltip = $localize`Videos with the most views during the last 24 hours`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.titleTooltip = $localize`Videos with the most views during the last ${this.trendingDays} days`
|
this.titleTooltip = $localize`Videos with the most views during the last ${trendingDays} days`
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
|
@ -5,6 +5,7 @@ import { EmptyComponent } from './empty.component'
|
||||||
import { HomepageRedirectComponent } from './homepage-redirect.component'
|
import { HomepageRedirectComponent } from './homepage-redirect.component'
|
||||||
import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators'
|
import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators'
|
||||||
import { ActorRedirectGuard } from './shared/shared-main/router/actor-redirect-guard.service'
|
import { ActorRedirectGuard } from './shared/shared-main/router/actor-redirect-guard.service'
|
||||||
|
import { VideosParentComponent } from './videos-parent.component'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -12,11 +13,6 @@ const routes: Routes = [
|
||||||
loadChildren: () => import('./+admin/routes'),
|
loadChildren: () => import('./+admin/routes'),
|
||||||
canActivateChild: [ MetaGuard ]
|
canActivateChild: [ MetaGuard ]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'home',
|
|
||||||
loadChildren: () => import('./+home/routes'),
|
|
||||||
canActivateChild: [ MetaGuard ]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'my-account',
|
path: 'my-account',
|
||||||
loadChildren: () => import('./+my-account/routes'),
|
loadChildren: () => import('./+my-account/routes'),
|
||||||
|
@ -128,11 +124,33 @@ const routes: Routes = [
|
||||||
preload: 5000
|
preload: 5000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// /home and other /videos routes
|
||||||
{
|
{
|
||||||
path: 'videos',
|
matcher: (url): UrlMatchResult => {
|
||||||
loadChildren: () => import('./+videos/routes'),
|
if (url.length < 1) return null
|
||||||
canActivateChild: [ MetaGuard ]
|
|
||||||
|
const matchResult = url[0].path === 'home' || url[0].path === 'videos'
|
||||||
|
if (!matchResult) return null
|
||||||
|
|
||||||
|
// So the children can detect the appropriate route
|
||||||
|
return { consumed: [] }
|
||||||
|
},
|
||||||
|
component: VideosParentComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
loadChildren: () => import('./+home/routes'),
|
||||||
|
canActivateChild: [ MetaGuard ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'videos',
|
||||||
|
loadChildren: () => import('./+videos/routes'),
|
||||||
|
canActivateChild: [ MetaGuard ]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'video-playlists/watch',
|
path: 'video-playlists/watch',
|
||||||
redirectTo: 'videos/watch/playlist'
|
redirectTo: 'videos/watch/playlist'
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import debug from 'debug'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
|
import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
|
||||||
|
import { VideoSortField } from '@peertube/peertube-models'
|
||||||
import { logger } from '@root-helpers/logger'
|
import { logger } from '@root-helpers/logger'
|
||||||
|
import { PluginsManager } from '@root-helpers/plugins-manager'
|
||||||
|
import debug from 'debug'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
import { ServerService } from '../server'
|
import { ServerService } from '../server'
|
||||||
import { SessionStorageService } from '../wrappers/storage.service'
|
import { SessionStorageService } from '../wrappers/storage.service'
|
||||||
import { PluginsManager } from '@root-helpers/plugins-manager'
|
|
||||||
import { environment } from 'src/environments/environment'
|
|
||||||
|
|
||||||
const debugLogger = debug('peertube:router:RedirectService')
|
const debugLogger = debug('peertube:router:RedirectService')
|
||||||
|
|
||||||
|
@ -70,7 +71,22 @@ export class RedirectService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultTrendingAlgorithm () {
|
getDefaultTrendingAlgorithm () {
|
||||||
return this.defaultTrendingAlgorithm
|
const algorithm = this.defaultTrendingAlgorithm
|
||||||
|
|
||||||
|
switch (algorithm) {
|
||||||
|
case 'most-viewed':
|
||||||
|
return '-trending'
|
||||||
|
|
||||||
|
case 'most-liked':
|
||||||
|
return '-likes'
|
||||||
|
|
||||||
|
// We'll automatically apply "best" sort if using "hot" sort with a logged user
|
||||||
|
case 'best':
|
||||||
|
return '-hot'
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '-' + algorithm as VideoSortField
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectToLatestSessionRoute () {
|
redirectToLatestSessionRoute () {
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<span class="instance-name">{{ instanceName }}</span>
|
<span class="instance-name">{{ instanceName }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="d-flex align-items-center" [hidden]="!loaded">
|
<div class="d-flex align-items-center" [hidden]="!loaded || (loggedIn && !user.account)">
|
||||||
<my-search-typeahead class="w-100 me-5"></my-search-typeahead>
|
<my-search-typeahead class="w-100 me-5"></my-search-typeahead>
|
||||||
|
|
||||||
@if (!loaded) {
|
@if (!loggedIn) {
|
||||||
<my-button class="me-3" icon="cog" (click)="openQuickSettings()"></my-button>
|
<my-button theme="tertiary" rounded="true" class="me-3" icon="cog" (click)="openQuickSettings()"></my-button>
|
||||||
|
|
||||||
<a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button peertube-button-link text-truncate d-block">
|
<a *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button peertube-button-link text-truncate d-block">
|
||||||
<my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
|
<my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
container="body" (openChange)="onDropdownOpenChange($event)"
|
container="body" (openChange)="onDropdownOpenChange($event)"
|
||||||
>
|
>
|
||||||
<button class="button-unstyle" ngbDropdownToggle>
|
<button class="button-unstyle" 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" class="me-2"></my-actor-avatar>
|
||||||
|
|
||||||
<div class="logged-in-info text-start">
|
<div class="logged-in-info text-start">
|
||||||
<div class="display-name ellipsis">{{ user.account?.displayName }}</div>
|
<div class="display-name ellipsis">{{ user.account?.displayName }}</div>
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
|
|
||||||
@include peertube-input-text(270px, 14px);
|
@include peertube-input-text(270px, 14px);
|
||||||
@include padding-left(42px); // For the search icon
|
|
||||||
@include padding-right(20px);
|
|
||||||
|
|
||||||
& {
|
& {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
|
padding-inline-start: 42px; // For the search icon
|
||||||
|
padding-inline-end: 20px; // For the search icon
|
||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<a
|
<a
|
||||||
tabindex="-1" class="d-flex flex-auto align-center p-2" [class.focus-visible]="active"
|
tabindex="-1" class="d-flex flex-auto align-items-center p-2" [class.focus-visible]="active"
|
||||||
[title]="getTitle()"
|
[title]="getTitle()"
|
||||||
[attr.aria-describedby]="describedby"
|
[attr.aria-describedby]="describedby"
|
||||||
>
|
>
|
||||||
<my-global-icon class="me-2" iconName="search"></my-global-icon>
|
<my-global-icon class="me-2" iconName="search"></my-global-icon>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex-auto overflow-hidden no-wrap d-flex align-center"
|
class="flex-auto overflow-hidden no-wrap d-flex align-items-center"
|
||||||
[attr.aria-label]="result.text"
|
[attr.aria-label]="result.text"
|
||||||
>
|
>
|
||||||
{{ result.text }}
|
{{ result.text }}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="margin-content">
|
||||||
|
<my-horizontal-menu [menuEntries]="menuEntries"></my-horizontal-menu>
|
||||||
|
</div>
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { RouterOutlet } from '@angular/router'
|
||||||
|
import { HorizontalMenuComponent } from '@app/shared/shared-main/menu/horizontal-menu.component'
|
||||||
|
import { ListOverflowItem } from '@app/shared/shared-main/menu/list-overflow.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-home-menu',
|
||||||
|
templateUrl: './home-menu.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
HorizontalMenuComponent,
|
||||||
|
RouterOutlet
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HomeMenuComponent {
|
||||||
|
menuEntries: ListOverflowItem[] = [
|
||||||
|
{ label: $localize`Home`, routerLink: '/home' },
|
||||||
|
{ label: $localize`Discover`, routerLink: '/videos/overview' },
|
||||||
|
{ label: $localize`Subscriptions`, routerLink: '/videos/subscriptions' },
|
||||||
|
{ label: $localize`Browse videos`, routerLink: '/videos/browse' }
|
||||||
|
]
|
||||||
|
}
|
|
@ -14,11 +14,11 @@
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<ng-container *ngFor="let menuSection of menuSections" >
|
<ng-container *ngFor="let menuSection of menuSections" >
|
||||||
<ul [ngClass]="[ menuSection.key, 'menu-block' ]">
|
<ul class="ul-unstyle" [ngClass]="[ menuSection.key, 'menu-block' ]">
|
||||||
<li i18n>
|
<li i18n>
|
||||||
<div class="block-title ellipsis" [ngClass]="{ 'visually-hidden': collapsed }">{{ menuSection.title }}</div>
|
<div class="block-title ellipsis" [ngClass]="{ 'visually-hidden': collapsed }">{{ menuSection.title }}</div>
|
||||||
|
|
||||||
<ul>
|
<ul class="ul-unstyle">
|
||||||
<li *ngFor="let link of menuSection.links">
|
<li *ngFor="let link of menuSection.links">
|
||||||
@if (link.isPrimaryButton === true) {
|
@if (link.isPrimaryButton === true) {
|
||||||
<my-button class="d-block menu-button" theme="primary" [icon]="link.icon" [ariaLabel]="link.label" [ptRouterLink]="link.path">
|
<my-button class="d-block menu-button" theme="primary" [icon]="link.icon" [ariaLabel]="link.label" [ptRouterLink]="link.path">
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
|
<div [ngClass]="{ 'visually-hidden': collapsed }" class="description">{{ shortDescription }}</div>
|
||||||
|
|
||||||
<my-button class="mt-2 d-block" theme="secondary" icon="help" i18n-ariaLabel aria-label="More info" i18n>
|
<my-button class="mt-2 d-block" theme="secondary" icon="help" i18n-ariaLabel aria-label="More info" i18n ptRouterLink="/about">
|
||||||
@if (!collapsed) {
|
@if (!collapsed) {
|
||||||
More info
|
More info
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,6 @@
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-wrapper {
|
.menu-wrapper {
|
||||||
--menuXPadding: 1.5rem;
|
--menuXPadding: 1.5rem;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="root">
|
||||||
|
<ng-template #linkTemplate let-item="item">
|
||||||
|
<a [routerLink]="item.routerLink" routerLinkActive="active" class="x-menu-entry">{{ item.label }}</a>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<my-list-overflow [items]="menuEntries" [itemTemplate]="linkTemplate"></my-list-overflow>
|
||||||
|
</div>
|
|
@ -0,0 +1,40 @@
|
||||||
|
@use '_variables' as *;
|
||||||
|
@use '_mixins' as *;
|
||||||
|
|
||||||
|
.root {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid pvar(--secondary-text-emphasis);
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-menu-entry {
|
||||||
|
color: pvar(--secondary);
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: $font-bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
@include font-size(22px);
|
||||||
|
@include disable-default-a-behaviour;
|
||||||
|
@include margin-right(2rem);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: pvar(--fg);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 4px;
|
||||||
|
background-color: pvar(--primary);
|
||||||
|
border-radius: 2px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: pvar(--fg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
|
||||||
|
import { ListOverflowComponent, ListOverflowItem } from './list-overflow.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-horizontal-menu',
|
||||||
|
templateUrl: './horizontal-menu.component.html',
|
||||||
|
styleUrls: [ './horizontal-menu.component.scss' ],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
RouterModule,
|
||||||
|
GlobalIconComponent,
|
||||||
|
ListOverflowComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HorizontalMenuComponent {
|
||||||
|
@Input() menuEntries: ListOverflowItem[] = []
|
||||||
|
}
|
|
@ -1,39 +1,42 @@
|
||||||
<div #itemsParent class="list-overflow-parent">
|
<div #itemsParent class="list-overflow-parent" [ngClass]="{ 'opacity-0': !initialized }">
|
||||||
<span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id">
|
|
||||||
<ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<ng-container *ngIf="isMenuDisplayed()">
|
<ul class="ul-unstyle d-flex">
|
||||||
<button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()">
|
<li class="d-inline-block" *ngFor="let item of items; index as id" [id]="getId(id)" #itemsRendered>
|
||||||
<span class="chevron-down"></span>
|
<ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
|
||||||
</button>
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div
|
@if (isMenuDisplayed()) {
|
||||||
*ngIf="!isInMobileView" class="list-overflow-menu"
|
@if (isInMobileView) {
|
||||||
ngbDropdown container="body" #dropdown="ngbDropdown"
|
<button type="button" class="peertube-button tertiary-button list-overflow-menu" (click)="toggleModal()">
|
||||||
(mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"
|
|
||||||
>
|
|
||||||
<button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }"
|
|
||||||
ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button"
|
|
||||||
>
|
|
||||||
<span class="chevron-down"></span>
|
<span class="chevron-down"></span>
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<div ngbDropdownMenu>
|
<ul class="ul-unstyle" ngbDropdownMenu>
|
||||||
<a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length"
|
<li *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length">
|
||||||
[routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
|
<a [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</ng-container>
|
}
|
||||||
</div >
|
</div >
|
||||||
|
|
||||||
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
<ng-template #modal let-close="close" let-dismiss="dismiss">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length"
|
<ul class="ul-unstyle">
|
||||||
[routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
|
<li *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length">
|
||||||
{{ item.label }}
|
<a [routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
|
||||||
</a>
|
{{ item.label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -6,21 +6,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-overflow-parent {
|
.list-overflow-parent {
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
// For the menu icon
|
// For the menu icon
|
||||||
|
position: relative;
|
||||||
max-width: calc(100vw - 30px);
|
max-width: calc(100vw - 30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-overflow-menu {
|
.list-overflow-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 25px;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 30px;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -36,13 +35,6 @@ button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .dropdown-menu {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
position: static;
|
|
||||||
right: auto;
|
|
||||||
bottom: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
a {
|
a {
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { lowerFirst, uniqueId } from 'lodash-es'
|
import { NgClass, NgFor, NgIf, NgTemplateOutlet, SlicePipe } from '@angular/common'
|
||||||
import { take } from 'rxjs/operators'
|
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
|
@ -13,11 +12,11 @@ import {
|
||||||
ViewChild,
|
ViewChild,
|
||||||
ViewChildren
|
ViewChildren
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||||
import { ScreenService } from '@app/core'
|
import { ScreenService } from '@app/core'
|
||||||
import { NgbDropdown, NgbModal, NgbDropdownAnchor, NgbDropdownMenu } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbDropdown, NgbDropdownMenu, NgbDropdownToggle, NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
import { RouterLinkActive, RouterLink } from '@angular/router'
|
import { lowerFirst, uniqueId } from 'lodash-es'
|
||||||
import { NgFor, NgTemplateOutlet, NgIf, NgClass, SlicePipe } from '@angular/common'
|
|
||||||
|
|
||||||
const debugLogger = debug('peertube:main:ListOverflowItem')
|
const debugLogger = debug('peertube:main:ListOverflowItem')
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ export interface ListOverflowItem {
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
NgIf,
|
NgIf,
|
||||||
NgbDropdown,
|
NgbDropdown,
|
||||||
NgbDropdownAnchor,
|
NgbDropdownToggle,
|
||||||
NgClass,
|
NgClass,
|
||||||
NgbDropdownMenu,
|
NgbDropdownMenu,
|
||||||
RouterLinkActive,
|
RouterLinkActive,
|
||||||
|
@ -54,10 +53,8 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
|
||||||
@ViewChildren('itemsRendered') itemsRendered: QueryList<ElementRef>
|
@ViewChildren('itemsRendered') itemsRendered: QueryList<ElementRef>
|
||||||
|
|
||||||
showItemsUntilIndexExcluded: number
|
showItemsUntilIndexExcluded: number
|
||||||
active = false
|
|
||||||
isInMobileView = false
|
isInMobileView = false
|
||||||
|
initialized = false
|
||||||
private openedOnHover = false
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private cdr: ChangeDetectorRef,
|
private cdr: ChangeDetectorRef,
|
||||||
|
@ -66,7 +63,10 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
setTimeout(() => this.onWindowResize(), 0)
|
setTimeout(() => {
|
||||||
|
this.onWindowResize()
|
||||||
|
this.initialized = true
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
isMenuDisplayed () {
|
isMenuDisplayed () {
|
||||||
|
@ -100,32 +100,6 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
|
||||||
this.cdr.markForCheck()
|
this.cdr.markForCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
openDropdownOnHover (dropdown: NgbDropdown) {
|
|
||||||
this.openedOnHover = true
|
|
||||||
dropdown.open()
|
|
||||||
|
|
||||||
// Menu was closed
|
|
||||||
dropdown.openChange
|
|
||||||
.pipe(take(1))
|
|
||||||
.subscribe(() => this.openedOnHover = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
dropdownAnchorClicked (dropdown: NgbDropdown) {
|
|
||||||
if (this.openedOnHover) {
|
|
||||||
this.openedOnHover = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return dropdown.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
closeDropdownIfHovered (dropdown: NgbDropdown) {
|
|
||||||
if (this.openedOnHover === false) return
|
|
||||||
|
|
||||||
dropdown.close()
|
|
||||||
this.openedOnHover = false
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleModal () {
|
toggleModal () {
|
||||||
this.modalService.open(this.modal, { centered: true })
|
this.modalService.open(this.modal, { centered: true })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<ul class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }">
|
<ul class="sub-menu ul-unstyle" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }">
|
||||||
<ng-container *ngFor="let menuEntry of menuEntries; index as id">
|
<ng-container *ngFor="let menuEntry of menuEntries; index as id">
|
||||||
@if (isDisplayed(menuEntry)) {
|
@if (isDisplayed(menuEntry)) {
|
||||||
@if (menuEntry.routerLink) {
|
@if (menuEntry.routerLink) {
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<li ngbDropdown #dropdown="ngbDropdown" autoClose="true" container="body">
|
<li ngbDropdown #dropdown="ngbDropdown" autoClose="true" container="body">
|
||||||
<button ngbDropdownToggle class="sub-menu-entry" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }">{{ menuEntry.label }}</button>
|
<button ngbDropdownToggle class="sub-menu-entry" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }">{{ menuEntry.label }}</button>
|
||||||
|
|
||||||
<ul ngbDropdownMenu>
|
<ul class="ul-unstyle" ngbDropdownMenu>
|
||||||
<li *ngFor="let menuChild of menuEntry.children">
|
<li *ngFor="let menuChild of menuEntry.children">
|
||||||
<a
|
<a
|
||||||
*ngIf="isDisplayed(menuChild)" ngbDropdownItem
|
*ngIf="isDisplayed(menuChild)" ngbDropdownItem
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-menu ::ng-deep .dropdown-toggle::after {
|
.sub-menu ::ng-deep .dropdown-toggle::after {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
.peertube-button {
|
.peertube-button {
|
||||||
// Prevent weird border radius blur on chrome
|
// Prevent weird border radius blur on chrome
|
||||||
z-index: unset !important;
|
z-index: unset !important;
|
||||||
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-toggle::after {
|
.dropdown-toggle::after {
|
||||||
|
|
|
@ -1,15 +1,5 @@
|
||||||
<div class="margin-content">
|
<div class="margin-content">
|
||||||
<div class="videos-header mb-4">
|
<div class="videos-header mb-4">
|
||||||
<h1 *ngIf="displayTitle" class="title mb-1" placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div [ngClass]="{ 'no-title': !displayTitle, invisible: !syndicationItems || syndicationItems.length === 0 }" class="title-subscription">
|
|
||||||
<ng-container i18n>Subscribe to RSS feed "{{ title }}"</ng-container>
|
|
||||||
|
|
||||||
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="headerActions.length !== 0" class="action-block mt-3">
|
<div *ngIf="headerActions.length !== 0" class="action-block mt-3">
|
||||||
<ng-container *ngFor="let action of headerActions">
|
<ng-container *ngFor="let action of headerActions">
|
||||||
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
|
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
|
||||||
|
@ -25,8 +15,11 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ng-template #actionContent let-action>
|
<ng-template #actionContent let-action>
|
||||||
<my-button *ngIf="!action.justIcon" [icon]="action.iconName" [label]="action.label"></my-button>
|
@if (action.justIcon) {
|
||||||
<my-button *ngIf="action.justIcon" [icon]="action.iconName" [ngbTooltip]="action.label"></my-button>
|
<my-button [icon]="action.iconName" [ngbTooltip]="action.label"></my-button>
|
||||||
|
} @else {
|
||||||
|
<my-button [icon]="action.iconName" [label]="action.label"></my-button>
|
||||||
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,42 +10,10 @@ $margin-top: 2rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
|
|
||||||
.title,
|
|
||||||
.title-subscription {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 18px;
|
|
||||||
color: pvar(--mainForegroundColor);
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: $font-semibold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-subscription {
|
|
||||||
grid-row: 2;
|
|
||||||
font-size: 14px;
|
|
||||||
color: pvar(--greyForegroundColor);
|
|
||||||
|
|
||||||
&.no-title {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-block {
|
.action-block {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
grid-row: 1/3;
|
grid-row: 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
my-feed {
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
color: pvar(--mainColor);
|
|
||||||
position: relative;
|
|
||||||
top: -2px;
|
|
||||||
|
|
||||||
@include margin-left(5px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-title {
|
.date-title {
|
||||||
|
|
|
@ -19,8 +19,8 @@ import { logger } from '@root-helpers/logger'
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
import { Observable, Subject, Subscription, forkJoin, fromEvent, of } from 'rxjs'
|
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 { InfiniteScrollerDirective } from '../shared-main/common/infinite-scroller.directive'
|
|
||||||
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 { FeedComponent } from '../shared-main/feeds/feed.component'
|
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'
|
||||||
|
@ -73,11 +73,6 @@ enum GroupDate {
|
||||||
export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
|
export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() getVideosObservableFunction: (pagination: ComponentPaginationLight, filters: VideoFilters) => Observable<ResultList<Video>>
|
@Input() getVideosObservableFunction: (pagination: ComponentPaginationLight, filters: VideoFilters) => Observable<ResultList<Video>>
|
||||||
@Input() getSyndicationItemsFunction: (filters: VideoFilters) => Promise<Syndication[]> | Syndication[]
|
@Input() getSyndicationItemsFunction: (filters: VideoFilters) => Promise<Syndication[]> | Syndication[]
|
||||||
@Input() baseRouteBuilderFunction: (filters: VideoFilters) => string[]
|
|
||||||
|
|
||||||
@Input() title: string
|
|
||||||
@Input() titleTooltip: string
|
|
||||||
@Input({ transform: booleanAttribute }) displayTitle = true
|
|
||||||
|
|
||||||
@Input() defaultSort: VideoSortField
|
@Input() defaultSort: VideoSortField
|
||||||
@Input() defaultScope: VideoFilterScope = 'federated'
|
@Input() defaultScope: VideoFilterScope = 'federated'
|
||||||
|
@ -426,20 +421,6 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
debugLogger('Will inject %O in URL query', queryParams)
|
debugLogger('Will inject %O in URL query', queryParams)
|
||||||
|
|
||||||
const baseRoute = this.baseRouteBuilderFunction
|
|
||||||
? this.baseRouteBuilderFunction(this.filters)
|
|
||||||
: []
|
|
||||||
|
|
||||||
const pathname = window.location.pathname
|
|
||||||
|
|
||||||
const baseRouteChanged = baseRoute.length !== 0 &&
|
|
||||||
pathname !== '/' && // Exclude special '/' case, we'll be redirected without component change
|
|
||||||
baseRoute.length !== 0 && pathname !== baseRoute.join('/')
|
|
||||||
|
|
||||||
if (baseRouteChanged || Object.keys(baseQuery).length !== 0 || customizedByUser) {
|
|
||||||
this.peertubeRouter.silentNavigate(baseRoute, queryParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filtersChanged.emit(this.filters)
|
this.filtersChanged.emit(this.filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<my-home-menu></my-home-menu>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { RouterOutlet } from '@angular/router'
|
||||||
|
import { HomeMenuComponent } from '@app/menu/home-menu.component'
|
||||||
|
import { DisableForReuseHook } from './core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './videos-parent.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
HomeMenuComponent,
|
||||||
|
RouterOutlet
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class VideosParentComponent implements DisableForReuseHook {
|
||||||
|
|
||||||
|
disableForReuse () {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledForReuse () {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1 @@
|
||||||
<svg width="4" height="16" viewBox="0 0 4 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<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-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
|
||||||
<path d="M2 8.875C2.48325 8.875 2.875 8.48325 2.875 8C2.875 7.51675 2.48325 7.125 2 7.125C1.51675 7.125 1.125 7.51675 1.125 8C1.125 8.48325 1.51675 8.875 2 8.875Z" fill="currentColor"/>
|
|
||||||
<path d="M2 8.875C2.48325 8.875 2.875 8.48325 2.875 8C2.875 7.51675 2.48325 7.125 2 7.125C1.51675 7.125 1.125 7.51675 1.125 8C1.125 8.48325 1.51675 8.875 2 8.875Z" fill="currentColor" stroke="currentColor" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 2.75C2.48325 2.75 2.875 2.35825 2.875 1.875C2.875 1.39175 2.48325 1 2 1C1.51675 1 1.125 1.39175 1.125 1.875C1.125 2.35825 1.51675 2.75 2 2.75Z" fill="currentColor"/>
|
|
||||||
<path d="M2 2.75C2.48325 2.75 2.875 2.35825 2.875 1.875C2.875 1.39175 2.48325 1 2 1C1.51675 1 1.125 1.39175 1.125 1.875C1.125 2.35825 1.51675 2.75 2 2.75Z" fill="currentColor" stroke="currentColor" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 15C2.48325 15 2.875 14.6082 2.875 14.125C2.875 13.6418 2.48325 13.25 2 13.25C1.51675 13.25 1.125 13.6418 1.125 14.125C1.125 14.6082 1.51675 15 2 15Z" fill="currentColor"/>
|
|
||||||
<path d="M2 15C2.48325 15 2.875 14.6082 2.875 14.125C2.875 13.6418 2.48325 13.25 2 13.25C1.51675 13.25 1.125 13.6418 1.125 14.125C1.125 14.6082 1.51675 15 2 15Z" fill="currentColor" stroke="currentColor" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 320 B |
|
@ -79,7 +79,6 @@ body {
|
||||||
|
|
||||||
--secondary-text-emphasis: #553D3D;
|
--secondary-text-emphasis: #553D3D;
|
||||||
|
|
||||||
|
|
||||||
font-family: $main-fonts;
|
font-family: $main-fonts;
|
||||||
font-weight: $font-regular;
|
font-weight: $font-regular;
|
||||||
color: pvar(--fg);
|
color: pvar(--fg);
|
||||||
|
@ -100,6 +99,11 @@ body {
|
||||||
background-color: pvar(--mainHoverColor);
|
background-color: pvar(--mainHoverColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force to override bootstrap utilities
|
||||||
|
body [hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
noscript,
|
noscript,
|
||||||
#incompatible-browser {
|
#incompatible-browser {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -149,3 +149,14 @@
|
||||||
.transform-rotate-180 {
|
.transform-rotate-180 {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.ul-unstyle {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
@include margin-right(55px);
|
@include margin-right(55px);
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-bottom-color: pvar(--mainColor);
|
border-bottom-color: pvar(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|
|
@ -18,6 +18,10 @@ export const clientFilterHookObject = {
|
||||||
'filter:api.recently-added-videos.videos.list.params': true,
|
'filter:api.recently-added-videos.videos.list.params': true,
|
||||||
'filter:api.recently-added-videos.videos.list.result': true,
|
'filter:api.recently-added-videos.videos.list.result': true,
|
||||||
|
|
||||||
|
// Filter params/result of the function that fetch videos of the browse videos page
|
||||||
|
'filter:api.browse-videos.videos.list.params': true,
|
||||||
|
'filter:api.browse-videos.videos.list.result': true,
|
||||||
|
|
||||||
// Filter params/result of the function that fetch videos of the user subscription page
|
// Filter params/result of the function that fetch videos of the user subscription page
|
||||||
'filter:api.user-subscriptions-videos.videos.list.params': true,
|
'filter:api.user-subscriptions-videos.videos.list.params': true,
|
||||||
'filter:api.user-subscriptions-videos.videos.list.result': true,
|
'filter:api.user-subscriptions-videos.videos.list.result': true,
|
||||||
|
|
Loading…
Reference in New Issue