mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-09-19 09:27:22 +03:00
feat: add support for sub routes under /my-account (#6218)
* feat: add support for sub routes under /my-account closes #6217 * feat(plugins/client-routes): page titles Add support for adding custom page titles in client routes. * fix(client/PluginPages): reload component upon URL change * Styling * docs(plugins): update registerClientRoute --------- Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
parent
9f92c8c426
commit
cd42491cf0
@ -1,8 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core'
|
|
||||||
import { AuthUser, ScreenService } from '@app/core'
|
|
||||||
import { TopMenuDropdownParam, TopMenuDropdownComponent } from '../shared/shared-main/misc/top-menu-dropdown.component'
|
|
||||||
import { RouterOutlet } from '@angular/router'
|
|
||||||
import { NgClass } from '@angular/common'
|
import { NgClass } from '@angular/common'
|
||||||
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { RouterOutlet } from '@angular/router'
|
||||||
|
import { AuthUser, PluginService, ScreenService } from '@app/core'
|
||||||
|
import { TopMenuDropdownComponent, TopMenuDropdownParam } from '../shared/shared-main/misc/top-menu-dropdown.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-my-account',
|
selector: 'my-my-account',
|
||||||
@ -15,17 +15,23 @@ export class MyAccountComponent implements OnInit {
|
|||||||
menuEntries: TopMenuDropdownParam[] = []
|
menuEntries: TopMenuDropdownParam[] = []
|
||||||
user: AuthUser
|
user: AuthUser
|
||||||
|
|
||||||
constructor (private screenService: ScreenService) { }
|
constructor (
|
||||||
|
private pluginService: PluginService,
|
||||||
|
private screenService: ScreenService
|
||||||
|
) { }
|
||||||
|
|
||||||
get isBroadcastMessageDisplayed () {
|
get isBroadcastMessageDisplayed () {
|
||||||
return this.screenService.isBroadcastMessageDisplayed
|
return this.screenService.isBroadcastMessageDisplayed
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit (): void {
|
ngOnInit (): void {
|
||||||
this.buildMenu()
|
this.pluginService.ensurePluginsAreLoaded('my-account')
|
||||||
|
.then(() => this.buildMenu())
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildMenu () {
|
private buildMenu () {
|
||||||
|
const clientRoutes = this.pluginService.getAllRegisteredClientRoutesForParent('/my-account') || {}
|
||||||
|
|
||||||
const moderationEntries: TopMenuDropdownParam = {
|
const moderationEntries: TopMenuDropdownParam = {
|
||||||
label: $localize`Moderation`,
|
label: $localize`Moderation`,
|
||||||
children: [
|
children: [
|
||||||
@ -68,7 +74,13 @@ export class MyAccountComponent implements OnInit {
|
|||||||
routerLink: '/my-account/applications'
|
routerLink: '/my-account/applications'
|
||||||
},
|
},
|
||||||
|
|
||||||
moderationEntries
|
moderationEntries,
|
||||||
|
|
||||||
|
...Object.values(clientRoutes)
|
||||||
|
.map(clientRoute => ({
|
||||||
|
label: clientRoute.menuItem?.label,
|
||||||
|
routerLink: '/my-account/p/' + clientRoute.route
|
||||||
|
}))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { BlocklistService } from '@app/shared/shared-moderation/blocklist.servic
|
|||||||
import { VideoBlockService } from '@app/shared/shared-moderation/video-block.service'
|
import { VideoBlockService } from '@app/shared/shared-moderation/video-block.service'
|
||||||
import { TwoFactorService } from '@app/shared/shared-users/two-factor.service'
|
import { TwoFactorService } from '@app/shared/shared-users/two-factor.service'
|
||||||
import { VideoCommentService } from '@app/shared/shared-video-comment/video-comment.service'
|
import { VideoCommentService } from '@app/shared/shared-video-comment/video-comment.service'
|
||||||
|
import { PluginPagesComponent } from '@app/shared/shared-plugin-pages/plugin-pages.component'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@ -160,6 +161,19 @@ export default [
|
|||||||
title: $localize`Import/Export`
|
title: $localize`Import/Export`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'p',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
component: PluginPagesComponent,
|
||||||
|
data: {
|
||||||
|
parentRoute: '/my-account',
|
||||||
|
pluginScope: 'my-account'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
|
||||||
import { PluginService } from '@app/core'
|
|
||||||
import { logger } from '@root-helpers/logger'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: './plugin-pages.component.html',
|
|
||||||
standalone: true
|
|
||||||
})
|
|
||||||
export class PluginPagesComponent implements AfterViewInit {
|
|
||||||
@ViewChild('root') root: ElementRef
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private router: Router,
|
|
||||||
private pluginService: PluginService
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit () {
|
|
||||||
this.pluginService.ensurePluginsAreLoaded('common')
|
|
||||||
.then(() => this.loadRoute())
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadRoute () {
|
|
||||||
const path = '/' + this.route.snapshot.url.map(u => u.path).join('/')
|
|
||||||
|
|
||||||
const registered = this.pluginService.getRegisteredClientRoute(path)
|
|
||||||
if (!registered) {
|
|
||||||
logger.info(`Could not find registered route ${path}`, this.pluginService.getAllRegisteredClientRoutes())
|
|
||||||
|
|
||||||
return this.router.navigate([ '/404' ], { skipLocationChange: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
registered.onMount({ rootEl: this.root.nativeElement })
|
|
||||||
}
|
|
||||||
}
|
|
@ -62,8 +62,11 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'p',
|
path: 'p',
|
||||||
loadChildren: () => import('./+plugin-pages/routes'),
|
loadChildren: () => import('./shared/shared-plugin-pages/routes'),
|
||||||
canActivateChild: [ MetaGuard ]
|
canActivateChild: [ MetaGuard ],
|
||||||
|
data: {
|
||||||
|
parentRoute: '/'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,11 @@ export class PluginService implements ClientHook {
|
|||||||
video: []
|
video: []
|
||||||
}
|
}
|
||||||
private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {}
|
private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {}
|
||||||
private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {}
|
private clientRoutes: {
|
||||||
|
[ parentRoute in RegisterClientRouteOptions['parentRoute'] ]?: {
|
||||||
|
[ route: string ]: RegisterClientRouteOptions
|
||||||
|
}
|
||||||
|
} = {}
|
||||||
|
|
||||||
private pluginsManager: PluginsManager
|
private pluginsManager: PluginsManager
|
||||||
|
|
||||||
@ -126,12 +130,29 @@ export class PluginService implements ClientHook {
|
|||||||
return this.settingsScripts[npmName]
|
return this.settingsScripts[npmName]
|
||||||
}
|
}
|
||||||
|
|
||||||
getRegisteredClientRoute (route: string) {
|
getRegisteredClientRoute (route: string, parentRoute: RegisterClientRouteOptions['parentRoute']) {
|
||||||
return this.clientRoutes[route]
|
if (!this.clientRoutes[parentRoute]) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clientRoutes[parentRoute][route]
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllRegisteredClientRoutesForParent (parentRoute: RegisterClientRouteOptions['parentRoute']) {
|
||||||
|
return this.clientRoutes[parentRoute]
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllRegisteredClientRoutes () {
|
getAllRegisteredClientRoutes () {
|
||||||
return Object.keys(this.clientRoutes)
|
return Object.keys(this.clientRoutes)
|
||||||
|
.map((parentRoute: RegisterClientRouteOptions['parentRoute']) => {
|
||||||
|
return Object.keys(this.clientRoutes[parentRoute])
|
||||||
|
.map(route => {
|
||||||
|
if (parentRoute === '/') return route
|
||||||
|
|
||||||
|
return parentRoute + route
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
async translateSetting (npmName: string, setting: RegisterClientFormFieldOptions) {
|
async translateSetting (npmName: string, setting: RegisterClientFormFieldOptions) {
|
||||||
@ -180,11 +201,17 @@ export class PluginService implements ClientHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onClientRoute (options: RegisterClientRouteOptions) {
|
private onClientRoute (options: RegisterClientRouteOptions) {
|
||||||
|
const parentRoute = options.parentRoute || '/'
|
||||||
|
|
||||||
const route = options.route.startsWith('/')
|
const route = options.route.startsWith('/')
|
||||||
? options.route
|
? options.route
|
||||||
: `/${options.route}`
|
: `/${options.route}`
|
||||||
|
|
||||||
this.clientRoutes[route] = options
|
if (!this.clientRoutes[parentRoute]) {
|
||||||
|
this.clientRoutes[parentRoute] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientRoutes[parentRoute][route] = options
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
|
private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
|
||||||
|
1
client/src/app/shared/shared-plugin-pages/index.ts
Normal file
1
client/src/app/shared/shared-plugin-pages/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './plugin-pages.component'
|
@ -0,0 +1,58 @@
|
|||||||
|
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
|
import { MetaService, PluginService } from '@app/core'
|
||||||
|
import { logger } from '@root-helpers/logger'
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './plugin-pages.component.html',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class PluginPagesComponent implements OnDestroy, AfterViewInit {
|
||||||
|
@ViewChild('root') root: ElementRef
|
||||||
|
|
||||||
|
private urlSub: Subscription
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private metaService: MetaService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private pluginService: PluginService
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
this.urlSub = this.route.url.subscribe(() => {
|
||||||
|
this.loadRoute()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
if (this.urlSub) this.urlSub.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadRoute () {
|
||||||
|
await this.pluginService.ensurePluginsAreLoaded(this.route.snapshot.data.pluginScope || 'common')
|
||||||
|
|
||||||
|
if (!this.route.snapshot.data.parentRoute) {
|
||||||
|
logger.error('Missing "parentRoute" URL data to load plugin route ' + this.route.snapshot.url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = '/' + this.route.snapshot.url.map(u => u.path).join('/')
|
||||||
|
|
||||||
|
const registered = this.pluginService.getRegisteredClientRoute(path, this.route.snapshot.data.parentRoute)
|
||||||
|
if (!registered) {
|
||||||
|
logger.info(`Could not find registered route ${path}`, { routes: this.pluginService.getAllRegisteredClientRoutes() })
|
||||||
|
|
||||||
|
return this.router.navigate([ '/404' ], { skipLocationChange: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registered.title) {
|
||||||
|
this.metaService.setTitle(registered.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
registered.onMount({ rootEl: this.root.nativeElement })
|
||||||
|
}
|
||||||
|
}
|
@ -70,7 +70,8 @@ class PluginsManager {
|
|||||||
'video-edit': new ReplaySubject<boolean>(1),
|
'video-edit': new ReplaySubject<boolean>(1),
|
||||||
'embed': new ReplaySubject<boolean>(1),
|
'embed': new ReplaySubject<boolean>(1),
|
||||||
'my-library': new ReplaySubject<boolean>(1),
|
'my-library': new ReplaySubject<boolean>(1),
|
||||||
'video-channel': new ReplaySubject<boolean>(1)
|
'video-channel': new ReplaySubject<boolean>(1),
|
||||||
|
'my-account': new ReplaySubject<boolean>(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly peertubeHelpersFactory: PeertubeHelpersFactory
|
private readonly peertubeHelpersFactory: PeertubeHelpersFactory
|
||||||
|
@ -8,4 +8,5 @@ export type PluginClientScope =
|
|||||||
'video-edit' |
|
'video-edit' |
|
||||||
'admin-plugin' |
|
'admin-plugin' |
|
||||||
'my-library' |
|
'my-library' |
|
||||||
'video-channel'
|
'video-channel' |
|
||||||
|
'my-account'
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
export interface RegisterClientRouteOptions {
|
export interface RegisterClientRouteOptions {
|
||||||
route: string
|
route: string
|
||||||
|
|
||||||
|
// Plugin route can be injected in a sub router, like the my-account page
|
||||||
|
parentRoute?: '/' | '/my-account'
|
||||||
|
// If parent route has a sub menu, specify the new entry sub menu settings
|
||||||
|
menuItem?: {
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
title?: string
|
||||||
|
|
||||||
onMount (options: {
|
onMount (options: {
|
||||||
rootEl: HTMLElement
|
rootEl: HTMLElement
|
||||||
}): void
|
}): void
|
||||||
|
@ -872,6 +872,11 @@ To create a client page, register a new client route:
|
|||||||
function register ({ registerClientRoute }) {
|
function register ({ registerClientRoute }) {
|
||||||
registerClientRoute({
|
registerClientRoute({
|
||||||
route: 'my-super/route',
|
route: 'my-super/route',
|
||||||
|
title: 'Page title for this route',
|
||||||
|
parentRoute: '/my-account', // Optional. The full path will be /my-account/p/my-super/route.
|
||||||
|
menuItem: { // Optional. This will add a menu item to this route. Only supported when parentRoute is '/my-account'.
|
||||||
|
label: 'Sub route',
|
||||||
|
},
|
||||||
onMount: ({ rootEl }) => {
|
onMount: ({ rootEl }) => {
|
||||||
rootEl.innerHTML = 'hello'
|
rootEl.innerHTML = 'hello'
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user