From d63e6d4604dfbe4938c7d66832c9202364c5bb64 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Dec 2021 15:01:12 +0100 Subject: Add ability for plugins to register client routes --- client/src/app/+plugin-pages/index.ts | 3 +++ .../+plugin-pages/plugin-pages-routing.module.ts | 19 +++++++++++++ .../app/+plugin-pages/plugin-pages.component.html | 1 + .../app/+plugin-pages/plugin-pages.component.ts | 31 ++++++++++++++++++++++ .../src/app/+plugin-pages/plugin-pages.module.ts | 21 +++++++++++++++ client/src/app/app-routing.module.ts | 6 +++++ client/src/app/core/plugins/plugin.service.ts | 31 +++++++++++++++++++--- .../src/app/core/routing/custom-reuse-strategy.ts | 2 +- client/src/root-helpers/plugins-manager.ts | 29 +++++++++++++++++--- client/src/standalone/videos/embed.ts | 2 +- client/src/types/register-client-option.model.ts | 9 +++++-- 11 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 client/src/app/+plugin-pages/index.ts create mode 100644 client/src/app/+plugin-pages/plugin-pages-routing.module.ts create mode 100644 client/src/app/+plugin-pages/plugin-pages.component.html create mode 100644 client/src/app/+plugin-pages/plugin-pages.component.ts create mode 100644 client/src/app/+plugin-pages/plugin-pages.module.ts (limited to 'client/src') diff --git a/client/src/app/+plugin-pages/index.ts b/client/src/app/+plugin-pages/index.ts new file mode 100644 index 000000000..b988f13f6 --- /dev/null +++ b/client/src/app/+plugin-pages/index.ts @@ -0,0 +1,3 @@ +export * from './plugin-pages-routing.module' +export * from './plugin-pages.component' +export * from './plugin-pages.module' diff --git a/client/src/app/+plugin-pages/plugin-pages-routing.module.ts b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts new file mode 100644 index 000000000..b47a787e0 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages-routing.module.ts @@ -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 {} diff --git a/client/src/app/+plugin-pages/plugin-pages.component.html b/client/src/app/+plugin-pages/plugin-pages.component.html new file mode 100644 index 000000000..cf62d1bd7 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.component.html @@ -0,0 +1 @@ +
diff --git a/client/src/app/+plugin-pages/plugin-pages.component.ts b/client/src/app/+plugin-pages/plugin-pages.component.ts new file mode 100644 index 000000000..5f294ee13 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.component.ts @@ -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 }) + } +} diff --git a/client/src/app/+plugin-pages/plugin-pages.module.ts b/client/src/app/+plugin-pages/plugin-pages.module.ts new file mode 100644 index 000000000..86f86c752 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.module.ts @@ -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 { } diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 438cb6512..42328d83d 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -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), diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts index 89391c2c5..fdbbd2d56 100644 --- a/client/src/app/core/plugins/plugin.service.ts +++ b/client/src/app/core/plugins/plugin.service.ts @@ -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' diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts index 1498e221f..5d3ad2e67 100644 --- a/client/src/app/core/routing/custom-reuse-strategy.ts +++ b/client/src/app/core/routing/custom-reuse-strategy.ts @@ -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 () { diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts index 9cba63373..e574e75a3 100644 --- a/client/src/root-helpers/plugins-manager.ts +++ b/client/src/root-helpers/plugins-manager.ts @@ -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)) } diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 9d1c6c443..874be580d 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -758,8 +758,8 @@ export class PeerTubeEmbed { return { getBaseStaticRoute: unimplemented, - getBaseRouterRoute: unimplemented, + getBasePluginClientPath: unimplemented, getSettings: unimplemented, diff --git a/client/src/types/register-client-option.model.ts b/client/src/types/register-client-option.model.ts index 3415ef08f..73f82e781 100644 --- a/client/src/types/register-client-option.model.ts +++ b/client/src/types/register-client-option.model.ts @@ -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 -- cgit v1.2.3