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 ] canActivateChild: [ MetaGuard ]
}, },
{
path: 'p',
loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule),
canActivateChild: [ MetaGuard ]
},
{ {
path: 'about', path: 'about',
loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), loadChildren: () => import('./+about/about.module').then(m => m.AboutModule),

View File

@ -20,7 +20,8 @@ import {
PluginType, PluginType,
PublicServerSetting, PublicServerSetting,
RegisterClientFormFieldOptions, RegisterClientFormFieldOptions,
RegisterClientSettingsScript, RegisterClientSettingsScriptOptions,
RegisterClientRouteOptions,
RegisterClientVideoFieldOptions, RegisterClientVideoFieldOptions,
ServerConfigPlugin ServerConfigPlugin
} from '@shared/models' } from '@shared/models'
@ -48,7 +49,8 @@ export class PluginService implements ClientHook {
private formFields: FormFields = { private formFields: FormFields = {
video: [] video: []
} }
private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {}
private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {}
private pluginsManager: PluginsManager private pluginsManager: PluginsManager
@ -67,7 +69,8 @@ export class PluginService implements ClientHook {
this.pluginsManager = new PluginsManager({ this.pluginsManager = new PluginsManager({
peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this),
onFormFields: this.onFormFields.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] return this.settingsScripts[npmName]
} }
getRegisteredClientRoute (route: string) {
return this.clientRoutes[route]
}
getAllRegisteredClientRoutes () {
return Object.keys(this.clientRoutes)
}
translateBy (npmName: string, toTranslate: string) { translateBy (npmName: string, toTranslate: string) {
const helpers = this.helpers[npmName] const helpers = this.helpers[npmName]
if (!helpers) { 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) const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
this.settingsScripts[npmName] = options 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 { private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
const { plugin } = pluginInfo const { plugin } = pluginInfo
const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) 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` return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router`
}, },
getBasePluginClientPath: () => {
return '/p'
},
getSettings: () => { getSettings: () => {
const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' 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 // Reuse the route if we're going to and from the same route
shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig return future.routeConfig === curr.routeConfig && future.routeConfig?.data?.reloadOnSameNavigation !== true
} }
private gb () { private gb () {

View File

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

View File

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

View File

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

View File

@ -4,4 +4,5 @@ export * from './plugin-element-placeholder.type'
export * from './plugin-selector-id.type' export * from './plugin-selector-id.type'
export * from './register-client-form-field.model' export * from './register-client-form-field.model'
export * from './register-client-hook.model' export * from './register-client-hook.model'
export * from './register-client-route.model'
export * from './register-client-settings-script.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' import { RegisterServerSettingOptions } from '../server'
export interface RegisterClientSettingsScript { export interface RegisterClientSettingsScriptOptions {
isSettingHidden (options: { isSettingHidden (options: {
setting: RegisterServerSettingOptions setting: RegisterServerSettingOptions
formValues: { [name: string]: any } formValues: { [name: string]: any }