Add ability for plugins to register client routes
This commit is contained in:
parent
03a65456f4
commit
d63e6d4604
14 changed files with 151 additions and 13 deletions
3
client/src/app/+plugin-pages/index.ts
Normal file
3
client/src/app/+plugin-pages/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './plugin-pages-routing.module'
|
||||
export * from './plugin-pages.component'
|
||||
export * from './plugin-pages.module'
|
19
client/src/app/+plugin-pages/plugin-pages-routing.module.ts
Normal file
19
client/src/app/+plugin-pages/plugin-pages-routing.module.ts
Normal 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 {}
|
1
client/src/app/+plugin-pages/plugin-pages.component.html
Normal file
1
client/src/app/+plugin-pages/plugin-pages.component.html
Normal file
|
@ -0,0 +1 @@
|
|||
<div #root></div>
|
31
client/src/app/+plugin-pages/plugin-pages.component.ts
Normal file
31
client/src/app/+plugin-pages/plugin-pages.component.ts
Normal 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 })
|
||||
}
|
||||
}
|
21
client/src/app/+plugin-pages/plugin-pages.module.ts
Normal file
21
client/src/app/+plugin-pages/plugin-pages.module.ts
Normal 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 { }
|
|
@ -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),
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -758,8 +758,8 @@ export class PeerTubeEmbed {
|
|||
|
||||
return {
|
||||
getBaseStaticRoute: unimplemented,
|
||||
|
||||
getBaseRouterRoute: unimplemented,
|
||||
getBasePluginClientPath: unimplemented,
|
||||
|
||||
getSettings: unimplemented,
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export interface RegisterClientRouteOptions {
|
||||
route: string
|
||||
|
||||
onMount (options: {
|
||||
rootEl: HTMLElement
|
||||
}): void
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { RegisterServerSettingOptions } from '../server'
|
||||
|
||||
export interface RegisterClientSettingsScript {
|
||||
export interface RegisterClientSettingsScriptOptions {
|
||||
isSettingHidden (options: {
|
||||
setting: RegisterServerSettingOptions
|
||||
formValues: { [name: string]: any }
|
||||
|
|
Loading…
Reference in a new issue