diff options
author | Chocobozzz <me@florianbigard.com> | 2021-12-10 15:01:12 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-12-10 15:01:12 +0100 |
commit | d63e6d4604dfbe4938c7d66832c9202364c5bb64 (patch) | |
tree | 6bd444be722276ff214d911284a400e374bdddc8 /client/src | |
parent | 03a65456f44a6152bb68975e29e076c8c5754cd6 (diff) | |
download | PeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.tar.gz PeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.tar.zst PeerTube-d63e6d4604dfbe4938c7d66832c9202364c5bb64.zip |
Add ability for plugins to register client routes
Diffstat (limited to 'client/src')
-rw-r--r-- | client/src/app/+plugin-pages/index.ts | 3 | ||||
-rw-r--r-- | client/src/app/+plugin-pages/plugin-pages-routing.module.ts | 19 | ||||
-rw-r--r-- | client/src/app/+plugin-pages/plugin-pages.component.html | 1 | ||||
-rw-r--r-- | client/src/app/+plugin-pages/plugin-pages.component.ts | 31 | ||||
-rw-r--r-- | client/src/app/+plugin-pages/plugin-pages.module.ts | 21 | ||||
-rw-r--r-- | client/src/app/app-routing.module.ts | 6 | ||||
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 31 | ||||
-rw-r--r-- | client/src/app/core/routing/custom-reuse-strategy.ts | 2 | ||||
-rw-r--r-- | client/src/root-helpers/plugins-manager.ts | 29 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 2 | ||||
-rw-r--r-- | client/src/types/register-client-option.model.ts | 9 |
11 files changed, 142 insertions, 12 deletions
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 @@ | |||
1 | export * from './plugin-pages-routing.module' | ||
2 | export * from './plugin-pages.component' | ||
3 | 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 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { PluginPagesComponent } from './plugin-pages.component' | ||
4 | |||
5 | const pluginPagesRoutes: Routes = [ | ||
6 | { | ||
7 | path: '**', | ||
8 | component: PluginPagesComponent, | ||
9 | data: { | ||
10 | reloadOnSameNavigation: true | ||
11 | } | ||
12 | } | ||
13 | ] | ||
14 | |||
15 | @NgModule({ | ||
16 | imports: [ RouterModule.forChild(pluginPagesRoutes) ], | ||
17 | exports: [ RouterModule ] | ||
18 | }) | ||
19 | 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 @@ | |||
<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 index 000000000..5f294ee13 --- /dev/null +++ b/client/src/app/+plugin-pages/plugin-pages.component.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { PluginService } from '@app/core' | ||
4 | |||
5 | @Component({ | ||
6 | templateUrl: './plugin-pages.component.html' | ||
7 | }) | ||
8 | export class PluginPagesComponent implements AfterViewInit { | ||
9 | @ViewChild('root') root: ElementRef | ||
10 | |||
11 | constructor ( | ||
12 | private route: ActivatedRoute, | ||
13 | private router: Router, | ||
14 | private pluginService: PluginService | ||
15 | ) { | ||
16 | |||
17 | } | ||
18 | |||
19 | ngAfterViewInit () { | ||
20 | const path = '/' + this.route.snapshot.url.map(u => u.path).join('/') | ||
21 | |||
22 | const registered = this.pluginService.getRegisteredClientRoute(path) | ||
23 | if (!registered) { | ||
24 | console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes()) | ||
25 | |||
26 | return this.router.navigate([ '/404' ], { skipLocationChange: true }) | ||
27 | } | ||
28 | |||
29 | registered.onMount({ rootEl: this.root.nativeElement }) | ||
30 | } | ||
31 | } | ||
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 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { PluginPagesRoutingModule } from './plugin-pages-routing.module' | ||
3 | import { PluginPagesComponent } from './plugin-pages.component' | ||
4 | |||
5 | @NgModule({ | ||
6 | imports: [ | ||
7 | PluginPagesRoutingModule | ||
8 | ], | ||
9 | |||
10 | declarations: [ | ||
11 | PluginPagesComponent | ||
12 | ], | ||
13 | |||
14 | exports: [ | ||
15 | PluginPagesComponent | ||
16 | ], | ||
17 | |||
18 | providers: [ | ||
19 | ] | ||
20 | }) | ||
21 | 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 | |||
@@ -58,6 +58,12 @@ const routes: Routes = [ | |||
58 | }, | 58 | }, |
59 | 59 | ||
60 | { | 60 | { |
61 | path: 'p', | ||
62 | loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule), | ||
63 | canActivateChild: [ MetaGuard ] | ||
64 | }, | ||
65 | |||
66 | { | ||
61 | path: 'about', | 67 | path: 'about', |
62 | loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), | 68 | loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), |
63 | canActivateChild: [ MetaGuard ] | 69 | canActivateChild: [ MetaGuard ] |
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 { | |||
20 | PluginType, | 20 | PluginType, |
21 | PublicServerSetting, | 21 | PublicServerSetting, |
22 | RegisterClientFormFieldOptions, | 22 | RegisterClientFormFieldOptions, |
23 | RegisterClientSettingsScript, | 23 | RegisterClientSettingsScriptOptions, |
24 | RegisterClientRouteOptions, | ||
24 | RegisterClientVideoFieldOptions, | 25 | RegisterClientVideoFieldOptions, |
25 | ServerConfigPlugin | 26 | ServerConfigPlugin |
26 | } from '@shared/models' | 27 | } from '@shared/models' |
@@ -48,7 +49,8 @@ export class PluginService implements ClientHook { | |||
48 | private formFields: FormFields = { | 49 | private formFields: FormFields = { |
49 | video: [] | 50 | video: [] |
50 | } | 51 | } |
51 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScript } = {} | 52 | private settingsScripts: { [ npmName: string ]: RegisterClientSettingsScriptOptions } = {} |
53 | private clientRoutes: { [ route: string ]: RegisterClientRouteOptions } = {} | ||
52 | 54 | ||
53 | private pluginsManager: PluginsManager | 55 | private pluginsManager: PluginsManager |
54 | 56 | ||
@@ -67,7 +69,8 @@ export class PluginService implements ClientHook { | |||
67 | this.pluginsManager = new PluginsManager({ | 69 | this.pluginsManager = new PluginsManager({ |
68 | peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), | 70 | peertubeHelpersFactory: this.buildPeerTubeHelpers.bind(this), |
69 | onFormFields: this.onFormFields.bind(this), | 71 | onFormFields: this.onFormFields.bind(this), |
70 | onSettingsScripts: this.onSettingsScripts.bind(this) | 72 | onSettingsScripts: this.onSettingsScripts.bind(this), |
73 | onClientRoute: this.onClientRoute.bind(this) | ||
71 | }) | 74 | }) |
72 | } | 75 | } |
73 | 76 | ||
@@ -123,6 +126,14 @@ export class PluginService implements ClientHook { | |||
123 | return this.settingsScripts[npmName] | 126 | return this.settingsScripts[npmName] |
124 | } | 127 | } |
125 | 128 | ||
129 | getRegisteredClientRoute (route: string) { | ||
130 | return this.clientRoutes[route] | ||
131 | } | ||
132 | |||
133 | getAllRegisteredClientRoutes () { | ||
134 | return Object.keys(this.clientRoutes) | ||
135 | } | ||
136 | |||
126 | translateBy (npmName: string, toTranslate: string) { | 137 | translateBy (npmName: string, toTranslate: string) { |
127 | const helpers = this.helpers[npmName] | 138 | const helpers = this.helpers[npmName] |
128 | if (!helpers) { | 139 | if (!helpers) { |
@@ -140,12 +151,20 @@ export class PluginService implements ClientHook { | |||
140 | }) | 151 | }) |
141 | } | 152 | } |
142 | 153 | ||
143 | private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) { | 154 | private onSettingsScripts (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) { |
144 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 155 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
145 | 156 | ||
146 | this.settingsScripts[npmName] = options | 157 | this.settingsScripts[npmName] = options |
147 | } | 158 | } |
148 | 159 | ||
160 | private onClientRoute (options: RegisterClientRouteOptions) { | ||
161 | const route = options.route.startsWith('/') | ||
162 | ? options.route | ||
163 | : `/${options.route}` | ||
164 | |||
165 | this.clientRoutes[route] = options | ||
166 | } | ||
167 | |||
149 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { | 168 | private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers { |
150 | const { plugin } = pluginInfo | 169 | const { plugin } = pluginInfo |
151 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) | 170 | const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType) |
@@ -161,6 +180,10 @@ export class PluginService implements ClientHook { | |||
161 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` | 180 | return environment.apiUrl + `${pathPrefix}/${plugin.name}/${plugin.version}/router` |
162 | }, | 181 | }, |
163 | 182 | ||
183 | getBasePluginClientPath: () => { | ||
184 | return '/p' | ||
185 | }, | ||
186 | |||
164 | getSettings: () => { | 187 | getSettings: () => { |
165 | const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' | 188 | const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings' |
166 | 189 | ||
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 { | |||
58 | 58 | ||
59 | // Reuse the route if we're going to and from the same route | 59 | // Reuse the route if we're going to and from the same route |
60 | shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { | 60 | shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { |
61 | return future.routeConfig === curr.routeConfig | 61 | return future.routeConfig === curr.routeConfig && future.routeConfig?.data?.reloadOnSameNavigation !== true |
62 | } | 62 | } |
63 | 63 | ||
64 | private gb () { | 64 | 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 { | |||
13 | PluginType, | 13 | PluginType, |
14 | RegisterClientFormFieldOptions, | 14 | RegisterClientFormFieldOptions, |
15 | RegisterClientHookOptions, | 15 | RegisterClientHookOptions, |
16 | RegisterClientSettingsScript, | 16 | RegisterClientRouteOptions, |
17 | RegisterClientSettingsScriptOptions, | ||
17 | RegisterClientVideoFieldOptions, | 18 | RegisterClientVideoFieldOptions, |
18 | RegisteredExternalAuthConfig, | 19 | RegisteredExternalAuthConfig, |
19 | ServerConfigPlugin | 20 | ServerConfigPlugin |
@@ -37,7 +38,8 @@ type PluginInfo = { | |||
37 | 38 | ||
38 | type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers | 39 | type PeertubeHelpersFactory = (pluginInfo: PluginInfo) => RegisterClientHelpers |
39 | type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | 40 | type OnFormFields = (options: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void |
40 | type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScript) => void | 41 | type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSettingsScriptOptions) => void |
42 | type OnClientRoute = (options: RegisterClientRouteOptions) => void | ||
41 | 43 | ||
42 | const logger = debug('peertube:plugins') | 44 | const logger = debug('peertube:plugins') |
43 | 45 | ||
@@ -64,15 +66,18 @@ class PluginsManager { | |||
64 | private readonly peertubeHelpersFactory: PeertubeHelpersFactory | 66 | private readonly peertubeHelpersFactory: PeertubeHelpersFactory |
65 | private readonly onFormFields: OnFormFields | 67 | private readonly onFormFields: OnFormFields |
66 | private readonly onSettingsScripts: OnSettingsScripts | 68 | private readonly onSettingsScripts: OnSettingsScripts |
69 | private readonly onClientRoute: OnClientRoute | ||
67 | 70 | ||
68 | constructor (options: { | 71 | constructor (options: { |
69 | peertubeHelpersFactory: PeertubeHelpersFactory | 72 | peertubeHelpersFactory: PeertubeHelpersFactory |
70 | onFormFields?: OnFormFields | 73 | onFormFields?: OnFormFields |
71 | onSettingsScripts?: OnSettingsScripts | 74 | onSettingsScripts?: OnSettingsScripts |
75 | onClientRoute?: OnClientRoute | ||
72 | }) { | 76 | }) { |
73 | this.peertubeHelpersFactory = options.peertubeHelpersFactory | 77 | this.peertubeHelpersFactory = options.peertubeHelpersFactory |
74 | this.onFormFields = options.onFormFields | 78 | this.onFormFields = options.onFormFields |
75 | this.onSettingsScripts = options.onSettingsScripts | 79 | this.onSettingsScripts = options.onSettingsScripts |
80 | this.onClientRoute = options.onClientRoute | ||
76 | } | 81 | } |
77 | 82 | ||
78 | static getPluginPathPrefix (isTheme: boolean) { | 83 | static getPluginPathPrefix (isTheme: boolean) { |
@@ -221,7 +226,7 @@ class PluginsManager { | |||
221 | return this.onFormFields(commonOptions, videoFormOptions) | 226 | return this.onFormFields(commonOptions, videoFormOptions) |
222 | } | 227 | } |
223 | 228 | ||
224 | const registerSettingsScript = (options: RegisterClientSettingsScript) => { | 229 | const registerSettingsScript = (options: RegisterClientSettingsScriptOptions) => { |
225 | if (!this.onSettingsScripts) { | 230 | if (!this.onSettingsScripts) { |
226 | throw new Error('Registering settings script is not supported') | 231 | throw new Error('Registering settings script is not supported') |
227 | } | 232 | } |
@@ -229,13 +234,29 @@ class PluginsManager { | |||
229 | return this.onSettingsScripts(pluginInfo, options) | 234 | return this.onSettingsScripts(pluginInfo, options) |
230 | } | 235 | } |
231 | 236 | ||
237 | const registerClientRoute = (options: RegisterClientRouteOptions) => { | ||
238 | if (!this.onClientRoute) { | ||
239 | throw new Error('Registering client route is not supported') | ||
240 | } | ||
241 | |||
242 | return this.onClientRoute(options) | ||
243 | } | ||
244 | |||
232 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) | 245 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) |
233 | 246 | ||
234 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | 247 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) |
235 | 248 | ||
236 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script | 249 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script |
237 | return dynamicImport(absURL) | 250 | return dynamicImport(absURL) |
238 | .then((script: ClientScriptModule) => script.register({ registerHook, registerVideoField, registerSettingsScript, peertubeHelpers })) | 251 | .then((script: ClientScriptModule) => { |
252 | return script.register({ | ||
253 | registerHook, | ||
254 | registerVideoField, | ||
255 | registerSettingsScript, | ||
256 | registerClientRoute, | ||
257 | peertubeHelpers | ||
258 | }) | ||
259 | }) | ||
239 | .then(() => this.sortHooksByPriority()) | 260 | .then(() => this.sortHooksByPriority()) |
240 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | 261 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) |
241 | } | 262 | } |
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 { | |||
758 | 758 | ||
759 | return { | 759 | return { |
760 | getBaseStaticRoute: unimplemented, | 760 | getBaseStaticRoute: unimplemented, |
761 | |||
762 | getBaseRouterRoute: unimplemented, | 761 | getBaseRouterRoute: unimplemented, |
762 | getBasePluginClientPath: unimplemented, | ||
763 | 763 | ||
764 | getSettings: unimplemented, | 764 | getSettings: unimplemented, |
765 | 765 | ||
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 @@ | |||
1 | import { | 1 | import { |
2 | RegisterClientFormFieldOptions, | 2 | RegisterClientFormFieldOptions, |
3 | RegisterClientHookOptions, | 3 | RegisterClientHookOptions, |
4 | RegisterClientSettingsScript, | 4 | RegisterClientRouteOptions, |
5 | RegisterClientSettingsScriptOptions, | ||
5 | RegisterClientVideoFieldOptions, | 6 | RegisterClientVideoFieldOptions, |
6 | ServerConfig | 7 | ServerConfig |
7 | } from '@shared/models' | 8 | } from '@shared/models' |
@@ -11,7 +12,9 @@ export type RegisterClientOptions = { | |||
11 | 12 | ||
12 | registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void | 13 | registerVideoField: (commonOptions: RegisterClientFormFieldOptions, videoFormOptions: RegisterClientVideoFieldOptions) => void |
13 | 14 | ||
14 | registerSettingsScript: (options: RegisterClientSettingsScript) => void | 15 | registerSettingsScript: (options: RegisterClientSettingsScriptOptions) => void |
16 | |||
17 | registerClientRoute: (options: RegisterClientRouteOptions) => void | ||
15 | 18 | ||
16 | peertubeHelpers: RegisterClientHelpers | 19 | peertubeHelpers: RegisterClientHelpers |
17 | } | 20 | } |
@@ -21,6 +24,8 @@ export type RegisterClientHelpers = { | |||
21 | 24 | ||
22 | getBaseRouterRoute: () => string | 25 | getBaseRouterRoute: () => string |
23 | 26 | ||
27 | getBasePluginClientPath: () => string | ||
28 | |||
24 | isLoggedIn: () => boolean | 29 | isLoggedIn: () => boolean |
25 | 30 | ||
26 | getAuthHeader: () => { 'Authorization': string } | undefined | 31 | getAuthHeader: () => { 'Authorization': string } | undefined |