Add ability for plugins to register client routes
This commit is contained in:
parent
03a65456f4
commit
d63e6d4604
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './plugin-pages-routing.module'
|
||||||
|
export * from './plugin-pages.component'
|
||||||
|
export * from './plugin-pages.module'
|
|
@ -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 {}
|
|
@ -0,0 +1 @@
|
||||||
|
<div #root></div>
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 ]
|
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),
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -758,8 +758,8 @@ export class PeerTubeEmbed {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getBaseStaticRoute: unimplemented,
|
getBaseStaticRoute: unimplemented,
|
||||||
|
|
||||||
getBaseRouterRoute: unimplemented,
|
getBaseRouterRoute: unimplemented,
|
||||||
|
getBasePluginClientPath: unimplemented,
|
||||||
|
|
||||||
getSettings: unimplemented,
|
getSettings: unimplemented,
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface RegisterClientRouteOptions {
|
||||||
|
route: string
|
||||||
|
|
||||||
|
onMount (options: {
|
||||||
|
rootEl: HTMLElement
|
||||||
|
}): void
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
|
Loading…
Reference in New Issue