]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add ability for plugins to register client routes
authorChocobozzz <me@florianbigard.com>
Fri, 10 Dec 2021 14:01:12 +0000 (15:01 +0100)
committerChocobozzz <me@florianbigard.com>
Fri, 10 Dec 2021 14:01:12 +0000 (15:01 +0100)
14 files changed:
client/src/app/+plugin-pages/index.ts [new file with mode: 0644]
client/src/app/+plugin-pages/plugin-pages-routing.module.ts [new file with mode: 0644]
client/src/app/+plugin-pages/plugin-pages.component.html [new file with mode: 0644]
client/src/app/+plugin-pages/plugin-pages.component.ts [new file with mode: 0644]
client/src/app/+plugin-pages/plugin-pages.module.ts [new file with mode: 0644]
client/src/app/app-routing.module.ts
client/src/app/core/plugins/plugin.service.ts
client/src/app/core/routing/custom-reuse-strategy.ts
client/src/root-helpers/plugins-manager.ts
client/src/standalone/videos/embed.ts
client/src/types/register-client-option.model.ts
shared/models/plugins/client/index.ts
shared/models/plugins/client/register-client-route.model.ts [new file with mode: 0644]
shared/models/plugins/client/register-client-settings-script.model.ts

diff --git a/client/src/app/+plugin-pages/index.ts b/client/src/app/+plugin-pages/index.ts
new file mode 100644 (file)
index 0000000..b988f13
--- /dev/null
@@ -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 (file)
index 0000000..b47a787
--- /dev/null
@@ -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 (file)
index 0000000..cf62d1b
--- /dev/null
@@ -0,0 +1 @@
+<div #root></div>
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 (file)
index 0000000..5f294ee
--- /dev/null
@@ -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 (file)
index 0000000..86f86c7
--- /dev/null
@@ -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 { }
index 438cb65125e3465effdf362365fa245eb4966804..42328d83ddff099ee0279015764bbd0eaf4c351a 100644 (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),
index 89391c2c5d0eda2f915fb5c4124764c1073105a0..fdbbd2d56dcfcc7940122ff947a39e0fb0d608f7 100644 (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'
 
index 1498e221fbf387533c4c9864b5be704045dca14e..5d3ad2e6751b44739123850322965d8fca35979a 100644 (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 () {
index 9cba633738254321066562c6c314dab8ec3b87da..e574e75a3cf1cb599a7377e13a75c674577314de 100644 (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))
   }
index 9d1c6c443bad92e490e7c04fcc8eda398aaf838b..874be580dbd9a72704005dc4b67b329e737fd00b 100644 (file)
@@ -758,8 +758,8 @@ export class PeerTubeEmbed {
 
     return {
       getBaseStaticRoute: unimplemented,
-
       getBaseRouterRoute: unimplemented,
+      getBasePluginClientPath: unimplemented,
 
       getSettings: unimplemented,
 
index 3415ef08fa4839555e58b8879046e20e878bcf72..73f82e78165fd787bb67b5088b89dd16061388e7 100644 (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
index c500185c91c3a67e24824988a63b7a74df8203aa..f3e3fcbcfdfb452540da27dc353b82397ea60fbf 100644 (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'
diff --git a/shared/models/plugins/client/register-client-route.model.ts b/shared/models/plugins/client/register-client-route.model.ts
new file mode 100644 (file)
index 0000000..271b678
--- /dev/null
@@ -0,0 +1,7 @@
+export interface RegisterClientRouteOptions {
+  route: string
+
+  onMount (options: {
+    rootEl: HTMLElement
+  }): void
+}
index 481ceef96ee6ce9e7001a169416b431fbfad28f0..117ca473962e0fd11a852275040b1a866b021db4 100644 (file)
@@ -1,6 +1,6 @@
 import { RegisterServerSettingOptions } from '../server'
 
-export interface RegisterClientSettingsScript {
+export interface RegisterClientSettingsScriptOptions {
   isSettingHidden (options: {
     setting: RegisterServerSettingOptions
     formValues: { [name: string]: any }