Add ability to filter menu links
This commit is contained in:
parent
9cc4b9c61f
commit
8beea2d37d
|
@ -82,6 +82,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
buildLandingPageOptions () {
|
buildLandingPageOptions () {
|
||||||
this.defaultLandingPageOptions = this.menuService.buildCommonLinks(this.serverConfig)
|
this.defaultLandingPageOptions = this.menuService.buildCommonLinks(this.serverConfig)
|
||||||
|
.links
|
||||||
.map(o => ({
|
.map(o => ({
|
||||||
id: o.path,
|
id: o.path,
|
||||||
label: o.label,
|
label: o.label,
|
||||||
|
|
|
@ -2,16 +2,23 @@ import { fromEvent } from 'rxjs'
|
||||||
import { debounceTime } from 'rxjs/operators'
|
import { debounceTime } from 'rxjs/operators'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { GlobalIconName } from '@app/shared/shared-icons'
|
import { GlobalIconName } from '@app/shared/shared-icons'
|
||||||
import { sortObjectComparator } from '@shared/core-utils/miscs/miscs'
|
|
||||||
import { HTMLServerConfig } from '@shared/models/server'
|
import { HTMLServerConfig } from '@shared/models/server'
|
||||||
import { ScreenService } from '../wrappers'
|
import { ScreenService } from '../wrappers'
|
||||||
|
|
||||||
export type MenuLink = {
|
export type MenuLink = {
|
||||||
icon: GlobalIconName
|
icon: GlobalIconName
|
||||||
|
|
||||||
label: string
|
label: string
|
||||||
menuLabel: string
|
// Used by the left menu for example
|
||||||
|
shortLabel: string
|
||||||
|
|
||||||
path: string
|
path: string
|
||||||
priority: number
|
}
|
||||||
|
|
||||||
|
export type MenuSection = {
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
links: MenuLink[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -59,51 +66,90 @@ export class MenuService {
|
||||||
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
|
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCommonLinks (config: HTMLServerConfig) {
|
buildLibraryLinks (userCanSeeVideosLink: boolean): MenuSection {
|
||||||
let entries: MenuLink[] = [
|
let links: MenuLink[] = []
|
||||||
|
|
||||||
|
if (userCanSeeVideosLink) {
|
||||||
|
links.push({
|
||||||
|
path: '/my-library/videos',
|
||||||
|
icon: 'videos' as GlobalIconName,
|
||||||
|
shortLabel: $localize`Videos`,
|
||||||
|
label: $localize`My videos`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
links = links.concat([
|
||||||
|
{
|
||||||
|
path: '/my-library/video-playlists',
|
||||||
|
icon: 'playlists' as GlobalIconName,
|
||||||
|
shortLabel: $localize`Playlists`,
|
||||||
|
label: $localize`My playlists`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/videos/subscriptions',
|
||||||
|
icon: 'subscriptions' as GlobalIconName,
|
||||||
|
shortLabel: $localize`Subscriptions`,
|
||||||
|
label: $localize`My subscriptions`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/my-library/history/videos',
|
||||||
|
icon: 'history' as GlobalIconName,
|
||||||
|
shortLabel: $localize`History`,
|
||||||
|
label: $localize`My history`
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'in-my-library',
|
||||||
|
title: 'In my library',
|
||||||
|
links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCommonLinks (config: HTMLServerConfig): MenuSection {
|
||||||
|
let links: MenuLink[] = []
|
||||||
|
|
||||||
|
if (config.homepage.enabled) {
|
||||||
|
links.push({
|
||||||
|
icon: 'home' as 'home',
|
||||||
|
label: $localize`Home`,
|
||||||
|
shortLabel: $localize`Home`,
|
||||||
|
path: '/home'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
links = links.concat([
|
||||||
{
|
{
|
||||||
icon: 'globe' as 'globe',
|
icon: 'globe' as 'globe',
|
||||||
label: $localize`Discover videos`,
|
label: $localize`Discover videos`,
|
||||||
menuLabel: $localize`Discover`,
|
shortLabel: $localize`Discover`,
|
||||||
path: '/videos/overview',
|
path: '/videos/overview'
|
||||||
priority: 150
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'trending' as 'trending',
|
icon: 'trending' as 'trending',
|
||||||
label: $localize`Trending videos`,
|
label: $localize`Trending videos`,
|
||||||
menuLabel: $localize`Trending`,
|
shortLabel: $localize`Trending`,
|
||||||
path: '/videos/trending',
|
path: '/videos/trending'
|
||||||
priority: 140
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'recently-added' as 'recently-added',
|
icon: 'recently-added' as 'recently-added',
|
||||||
label: $localize`Recently added videos`,
|
label: $localize`Recently added videos`,
|
||||||
menuLabel: $localize`Recently added`,
|
shortLabel: $localize`Recently added`,
|
||||||
path: '/videos/recently-added',
|
path: '/videos/recently-added'
|
||||||
priority: 130
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'local' as 'local',
|
icon: 'local' as 'local',
|
||||||
label: $localize`Local videos`,
|
label: $localize`Local videos`,
|
||||||
menuLabel: $localize`Local videos`,
|
shortLabel: $localize`Local videos`,
|
||||||
path: '/videos/local',
|
path: '/videos/local'
|
||||||
priority: 120
|
|
||||||
}
|
}
|
||||||
]
|
])
|
||||||
|
|
||||||
if (config.homepage.enabled) {
|
return {
|
||||||
entries.push({
|
key: 'on-instance',
|
||||||
icon: 'home' as 'home',
|
title: $localize`ON ${config.instance.name}`,
|
||||||
label: $localize`Home`,
|
links
|
||||||
menuLabel: $localize`Home`,
|
|
||||||
path: '/home',
|
|
||||||
priority: 160
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = entries.sort(sortObjectComparator('priority', 'desc'))
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleWindowResize () {
|
private handleWindowResize () {
|
||||||
|
|
|
@ -95,39 +95,16 @@
|
||||||
<a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link create-account-button">Create an account</a>
|
<a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="peertube-button-link create-account-button">Create an account</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isLoggedIn" class="in-my-library">
|
<ng-container *ngFor="let menuSection of menuSections" >
|
||||||
<div i18n class="block-title">IN MY LIBRARY</div>
|
<div [ngClass]="[ menuSection.key, 'menu-block' ]">
|
||||||
|
<div i18n class="block-title">{{ menuSection.title }}</div>
|
||||||
|
|
||||||
<a *ngIf="user.canSeeVideosLink" class="menu-link" routerLink="/my-library/videos" routerLinkActive="active">
|
<a class="menu-link" *ngFor="let link of menuSection.links" [routerLink]="link.path" routerLinkActive="active">
|
||||||
<my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
|
<my-global-icon [iconName]="link.icon" aria-hidden="true"></my-global-icon>
|
||||||
<ng-container i18n>Videos</ng-container>
|
<ng-container>{{ link.shortLabel }}</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
<a class="menu-link" routerLink="/my-library/video-playlists" routerLinkActive="active">
|
</ng-container>
|
||||||
<my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon>
|
|
||||||
<ng-container i18n>Playlists</ng-container>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="menu-link" routerLink="/videos/subscriptions" routerLinkActive="active">
|
|
||||||
<my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon>
|
|
||||||
<ng-container i18n>Subscriptions</ng-container>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="menu-link" routerLink="/my-library/history/videos" routerLinkActive="active">
|
|
||||||
<my-global-icon iconName="history" aria-hidden="true"></my-global-icon>
|
|
||||||
<ng-container i18n>History</ng-container>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="on-instance">
|
|
||||||
<div i18n class="block-title">ON {{instanceName}}</div>
|
|
||||||
|
|
||||||
<a class="menu-link" *ngFor="let commonLink of commonMenuLinks" [routerLink]="commonLink.path" routerLinkActive="active">
|
|
||||||
<my-global-icon [iconName]="commonLink.icon" aria-hidden="true"></my-global-icon>
|
|
||||||
<ng-container>{{ commonLink.menuLabel }}</ng-container>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
|
|
@ -274,8 +274,7 @@ my-actor-avatar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.in-my-library,
|
.menu-block,
|
||||||
.on-instance,
|
|
||||||
.footer-block {
|
.footer-block {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
AuthService,
|
AuthService,
|
||||||
AuthStatus,
|
AuthStatus,
|
||||||
AuthUser,
|
AuthUser,
|
||||||
MenuLink,
|
HooksService,
|
||||||
|
MenuSection,
|
||||||
MenuService,
|
MenuService,
|
||||||
RedirectService,
|
RedirectService,
|
||||||
ScreenService,
|
ScreenService,
|
||||||
|
@ -45,7 +46,7 @@ export class MenuComponent implements OnInit {
|
||||||
|
|
||||||
currentInterfaceLanguage: string
|
currentInterfaceLanguage: string
|
||||||
|
|
||||||
commonMenuLinks: MenuLink[] = []
|
menuSections: MenuSection[] = []
|
||||||
|
|
||||||
private languages: VideoConstant<string>[] = []
|
private languages: VideoConstant<string>[] = []
|
||||||
|
|
||||||
|
@ -71,7 +72,8 @@ export class MenuComponent implements OnInit {
|
||||||
private screenService: ScreenService,
|
private screenService: ScreenService,
|
||||||
private menuService: MenuService,
|
private menuService: MenuService,
|
||||||
private modalService: PeertubeModalService,
|
private modalService: PeertubeModalService,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private hooks: HooksService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get isInMobileView () {
|
get isInMobileView () {
|
||||||
|
@ -88,46 +90,23 @@ export class MenuComponent implements OnInit {
|
||||||
return this.languageChooserModal.getCurrentLanguage()
|
return this.languageChooserModal.getCurrentLanguage()
|
||||||
}
|
}
|
||||||
|
|
||||||
get instanceName () {
|
|
||||||
return this.htmlServerConfig.instance.name
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.htmlServerConfig = this.serverService.getHTMLConfig()
|
this.htmlServerConfig = this.serverService.getHTMLConfig()
|
||||||
this.buildMenuLinks()
|
|
||||||
|
|
||||||
this.isLoggedIn = this.authService.isLoggedIn()
|
|
||||||
if (this.isLoggedIn === true) {
|
|
||||||
this.user = this.authService.getUser()
|
|
||||||
|
|
||||||
this.computeNSFWPolicy()
|
|
||||||
this.computeVideosLink()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.computeAdminAccess()
|
|
||||||
|
|
||||||
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
||||||
|
|
||||||
|
this.updateUserState()
|
||||||
|
this.buildMenuSections()
|
||||||
|
|
||||||
this.authService.loginChangedSource.subscribe(
|
this.authService.loginChangedSource.subscribe(
|
||||||
status => {
|
status => {
|
||||||
if (status === AuthStatus.LoggedIn) {
|
if (status === AuthStatus.LoggedIn) {
|
||||||
this.isLoggedIn = true
|
this.isLoggedIn = true
|
||||||
this.user = this.authService.getUser()
|
|
||||||
|
|
||||||
this.computeAdminAccess()
|
|
||||||
this.computeVideosLink()
|
|
||||||
|
|
||||||
logger('Logged in.')
|
|
||||||
} else if (status === AuthStatus.LoggedOut) {
|
} else if (status === AuthStatus.LoggedOut) {
|
||||||
this.isLoggedIn = false
|
this.isLoggedIn = false
|
||||||
this.user = undefined
|
|
||||||
|
|
||||||
this.computeAdminAccess()
|
|
||||||
|
|
||||||
logger('Logged out.')
|
|
||||||
} else {
|
|
||||||
console.error('Unknown auth status: ' + status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateUserState()
|
||||||
|
this.buildMenuSections()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -257,8 +236,20 @@ export class MenuComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildMenuLinks () {
|
private async buildMenuSections () {
|
||||||
this.commonMenuLinks = this.menuService.buildCommonLinks(this.htmlServerConfig)
|
const menuSections = []
|
||||||
|
|
||||||
|
if (this.isLoggedIn) {
|
||||||
|
menuSections.push(
|
||||||
|
this.menuService.buildLibraryLinks(this.user?.canSeeVideosLink)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
menuSections.push(
|
||||||
|
this.menuService.buildCommonLinks(this.htmlServerConfig)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.menuSections = await this.hooks.wrapObject(menuSections, 'common', 'filter:left-menu.links.create.result')
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildUserLanguages () {
|
private buildUserLanguages () {
|
||||||
|
@ -268,7 +259,7 @@ export class MenuComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.user.videoLanguages) {
|
if (!this.user.videoLanguages) {
|
||||||
this.videoLanguages = [$localize`any language`]
|
this.videoLanguages = [ $localize`any language` ]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +275,8 @@ export class MenuComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeVideosLink () {
|
private computeVideosLink () {
|
||||||
|
if (!this.isLoggedIn) return
|
||||||
|
|
||||||
this.authService.userInformationLoaded
|
this.authService.userInformationLoaded
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
|
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
|
||||||
|
@ -313,4 +306,14 @@ export class MenuComponent implements OnInit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateUserState () {
|
||||||
|
this.user = this.isLoggedIn
|
||||||
|
? this.authService.getUser()
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
this.computeAdminAccess()
|
||||||
|
this.computeNSFWPolicy()
|
||||||
|
this.computeVideosLink()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,10 @@ export const clientFilterHookObject = {
|
||||||
|
|
||||||
// Filter our SVG icons content
|
// Filter our SVG icons content
|
||||||
'filter:internal.common.svg-icons.get-content.params': true,
|
'filter:internal.common.svg-icons.get-content.params': true,
|
||||||
'filter:internal.common.svg-icons.get-content.result': true
|
'filter:internal.common.svg-icons.get-content.result': true,
|
||||||
|
|
||||||
|
// Filter left menu links
|
||||||
|
'filter:left-menu.links.create.result': true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientFilterHookName = keyof typeof clientFilterHookObject
|
export type ClientFilterHookName = keyof typeof clientFilterHookObject
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
- [Add custom fields to video form](#add-custom-fields-to-video-form)
|
- [Add custom fields to video form](#add-custom-fields-to-video-form)
|
||||||
- [Register settings script](#register-settings-script)
|
- [Register settings script](#register-settings-script)
|
||||||
- [HTML placeholder elements](#html-placeholder-elements)
|
- [HTML placeholder elements](#html-placeholder-elements)
|
||||||
|
- [Add/remove left menu links](#addremove-left-menu-links)
|
||||||
- [Publishing](#publishing)
|
- [Publishing](#publishing)
|
||||||
- [Write a plugin/theme](#write-a-plugintheme)
|
- [Write a plugin/theme](#write-a-plugintheme)
|
||||||
- [Clone the quickstart repository](#clone-the-quickstart-repository)
|
- [Clone the quickstart repository](#clone-the-quickstart-repository)
|
||||||
|
@ -744,6 +745,11 @@ async function register (...) {
|
||||||
|
|
||||||
See the complete list on https://docs.joinpeertube.org/api-plugins
|
See the complete list on https://docs.joinpeertube.org/api-plugins
|
||||||
|
|
||||||
|
#### Add/remove left menu links
|
||||||
|
|
||||||
|
Left menu links can be filtered (add/remove a section or add/remove links) using the `filter:left-menu.links.create.result` client hook.
|
||||||
|
|
||||||
|
|
||||||
### Publishing
|
### Publishing
|
||||||
|
|
||||||
PeerTube plugins and themes should be published on [NPM](https://www.npmjs.com/) so that PeerTube indexes
|
PeerTube plugins and themes should be published on [NPM](https://www.npmjs.com/) so that PeerTube indexes
|
||||||
|
|
Loading…
Reference in New Issue