1
0
Fork 0

Add ability for plugins to register client routes

This commit is contained in:
Chocobozzz 2021-12-10 15:01:12 +01:00
parent 03a65456f4
commit d63e6d4604
No known key found for this signature in database
GPG key ID: 583A612D890159BE
14 changed files with 151 additions and 13 deletions

View file

@ -0,0 +1,3 @@
export * from './plugin-pages-routing.module'
export * from './plugin-pages.component'
export * from './plugin-pages.module'

View file

@ -0,0 +1,19 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { PluginPagesComponent } from './plugin-pages.component'
const pluginPagesRoutes: Routes = [
{
path: '**',
component: PluginPagesComponent,
data: {
reloadOnSameNavigation: true
}
}
]
@NgModule({
imports: [ RouterModule.forChild(pluginPagesRoutes) ],
exports: [ RouterModule ]
})
export class PluginPagesRoutingModule {}

View file

@ -0,0 +1 @@
<div #root></div>

View file

@ -0,0 +1,31 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { PluginService } from '@app/core'
@Component({
templateUrl: './plugin-pages.component.html'
})
export class PluginPagesComponent implements AfterViewInit {
@ViewChild('root') root: ElementRef
constructor (
private route: ActivatedRoute,
private router: Router,
private pluginService: PluginService
) {
}
ngAfterViewInit () {
const path = '/' + this.route.snapshot.url.map(u => u.path).join('/')
const registered = this.pluginService.getRegisteredClientRoute(path)
if (!registered) {
console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes())
return this.router.navigate([ '/404' ], { skipLocationChange: true })
}
registered.onMount({ rootEl: this.root.nativeElement })
}
}

View file

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core'
import { PluginPagesRoutingModule } from './plugin-pages-routing.module'
import { PluginPagesComponent } from './plugin-pages.component'
@NgModule({
imports: [
PluginPagesRoutingModule
],
declarations: [
PluginPagesComponent
],
exports: [
PluginPagesComponent
],
providers: [
]
})
export class PluginPagesModule { }

View file

@ -57,6 +57,12 @@ const routes: Routes = [
canActivateChild: [ MetaGuard ]
},
{
path: 'p',
loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule),
canActivateChild: [ MetaGuard ]
},
{
path: 'about',
loadChildren: () => import('./+about/about.module').then(m => m.AboutModule),

View file

@ -20,7 +20,8 @@ import {
PluginType,
PublicServerSetting,
RegisterClientFormFieldOptions,
RegisterClientSettingsScript,
RegisterClientSettingsScriptOptions,
RegisterClientRouteOptions,
RegisterClientVideoFieldOptions,
ServerConfigPlugin
} from '@shared/models'
@ -48,7 +49,8 @@ export class PluginService implements ClientHook {
private formFields: FormFields = {
video: []
}
private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {}
private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {}
private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {}
private pluginsManager: PluginsManager
@ -67,7 +69,8 @@ export class PluginService implements ClientHook {
this.pluginsManager = new PluginsManager({
peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this),
onFormFields: this.onFormFields.bind(this),
onSettingsScripts: this.onSettingsScripts.bind(this)
onSettingsScripts: this.onSettingsScripts.bind(this),
onClientRoute: this.onClientRoute.bind(this)
})
}
@ -123,6 +126,14 @@ export class PluginService implements ClientHook {
return this.settingsScripts[npmName]
}
getRegisteredClientRoute (route: string) {
return this.clientRoutes[route]
}
getAllRegisteredClientRoutes () {
return Object.keys(this.clientRoutes)
}
translateBy (npmName: string, toTranslate: string) {
const helpers = this.helpers[npmName]
if (!helpers) {
@ -140,12 +151,20 @@ export class PluginService implements ClientHook {
})
}
private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) {
private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) {
const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
this.settingsScripts[npmName] = options
}
private onClientRoute (options: RegisterClientRouteOptions) {
const route = options.route.startsWith('/')
? options.route
: `/${options.route}`
this.clientRoutes[route] = options
}
private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
const { plugin } = pluginInfo
const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
@ -161,6 +180,10 @@ export class PluginService implements ClientHook {
return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router`
},
getBasePluginClientPath: () => {
return '/p'
},
getSettings: () => {
const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings'

View file

@ -58,7 +58,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
// Reuse the route if we're going to and from the same route
shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig
return future.routeConfig === curr.routeConfig && future.routeConfig?.data?.reloadOnSameNavigation !== true
}
private gb () {

View file

@ -13,7 +13,8 @@ import {
PluginType,
RegisterClientFormFieldOptions,
RegisterClientHookOptions,
RegisterClientSettingsScript,
RegisterClientRouteOptions,
RegisterClientSettingsScriptOptions,
RegisterClientVideoFieldOptions,
RegisteredExternalAuthConfig,
ServerConfigPlugin
@ -37,7 +38,8 @@ type PluginInfo = {
type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers
type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void
type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) => void
type OnClientRoute = (options: RegisterClientRouteOptions) => void
const logger = debug('peertube:plugins')
@ -64,15 +66,18 @@ class PluginsManager {
private readonly peertubeHelpersFactory: PeertubeHelpersFactory
private readonly onFormFields: OnFormFields
private readonly onSettingsScripts: OnSettingsScripts
private readonly onClientRoute: OnClientRoute
constructor (options: {
peertubeHelpersFactory: PeertubeHelpersFactory
onFormFields?: OnFormFields
onSettingsScripts?: OnSettingsScripts
onClientRoute?: OnClientRoute
}) {
this.peertubeHelpersFactory = options.peertubeHelpersFactory
this.onFormFields = options.onFormFields
this.onSettingsScripts = options.onSettingsScripts
this.onClientRoute = options.onClientRoute
}
static getPluginPathPrefix (isTheme: boolean) {
@ -221,7 +226,7 @@ class PluginsManager {
return this.onFormFields(commonOptions, videoFormOptions)
}
const registerSettingsScript = (options: RegisterClientSettingsScript) => {
const registerSettingsScript = (options: RegisterClientSettingsScriptOptions) => {
if (!this.onSettingsScripts) {
throw new Error('Registering settings script is not supported')
}
@ -229,13 +234,29 @@ class PluginsManager {
return this.onSettingsScripts(pluginInfo, options)
}
const registerClientRoute = (options: RegisterClientRouteOptions) => {
if (!this.onClientRoute) {
throw new Error('Registering client route is not supported')
}
return this.onClientRoute(options)
}
const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo)
console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
return dynamicImport(absURL)
.then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers }))
.then((script: ClientScriptModule) => {
return script.register({
registerHook,
registerVideoField,
registerSettingsScript,
registerClientRoute,
peertubeHelpers
})
})
.then(() => this.sortHooksByPriority())
.catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
}

View file

@ -758,8 +758,8 @@ export class PeerTubeEmbed {
return {
getBaseStaticRoute: unimplemented,
getBaseRouterRoute: unimplemented,
getBasePluginClientPath: unimplemented,
getSettings: unimplemented,

View file

@ -1,7 +1,8 @@
import {
RegisterClientFormFieldOptions,
RegisterClientHookOptions,
RegisterClientSettingsScript,
RegisterClientRouteOptions,
RegisterClientSettingsScriptOptions,
RegisterClientVideoFieldOptions,
ServerConfig
} from '@shared/models'
@ -11,7 +12,9 @@ export type RegisterClientOptions = {
registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void
registerSettingsScript: (options: RegisterClientSettingsScript) => void
registerSettingsScript: (options: RegisterClientSettingsScriptOptions) => void
registerClientRoute: (options: RegisterClientRouteOptions) => void
peertubeHelpers: RegisterClientHelpers
}
@ -21,6 +24,8 @@ export type RegisterClientHelpers = {
getBaseRouterRoute: () => string
getBasePluginClientPath: () => string
isLoggedIn: () => boolean
getAuthHeader: () => { 'Authorization': string } | undefined

View file

@ -4,4 +4,5 @@ export * from './plugin-element-placeholder.type'
export * from './plugin-selector-id.type'
export * from './register-client-form-field.model'
export * from './register-client-hook.model'
export * from './register-client-route.model'
export * from './register-client-settings-script.model'

View file

@ -0,0 +1,7 @@
export interface RegisterClientRouteOptions {
route: string
onMount (options: {
rootEl: HTMLElement
}): void
}

View file

@ -1,6 +1,6 @@
import { RegisterServerSettingOptions } from '../server'
export interface RegisterClientSettingsScript {
export interface RegisterClientSettingsScriptOptions {
isSettingHidden (options: {
setting: RegisterServerSettingOptions
formValues: { [name: string]: any }