Add client hooks
This commit is contained in:
parent
587568e1cc
commit
93cae47925
|
@ -68,7 +68,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
|
||||||
switchMap(res => from(res.data)),
|
switchMap(res => from(res.data)),
|
||||||
concatMap(videoChannel => {
|
concatMap(videoChannel => {
|
||||||
return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort)
|
return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort)
|
||||||
.pipe(map(data => ({ videoChannel, videos: data.videos })))
|
.pipe(map(data => ({ videoChannel, videos: data.data })))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.subscribe(({ videoChannel, videos }) => {
|
.subscribe(({ videoChannel, videos }) => {
|
||||||
|
|
|
@ -69,8 +69,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
|
||||||
return this.videoService
|
return this.videoService
|
||||||
.getAccountVideos(this.account, newPagination, this.sort)
|
.getAccountVideos(this.account, newPagination, this.sort)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(({ totalVideos }) => {
|
tap(({ total }) => {
|
||||||
this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos })
|
this.titlePage = this.i18n('Published {{total}} videos', { total })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,9 +131,9 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
|
||||||
|
|
||||||
private loadElements () {
|
private loadElements () {
|
||||||
this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination)
|
this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination)
|
||||||
.subscribe(({ totalVideos, videos }) => {
|
.subscribe(({ total, data }) => {
|
||||||
this.videos = this.videos.concat(videos)
|
this.videos = this.videos.concat(data)
|
||||||
this.pagination.totalItems = totalVideos
|
this.pagination.totalItems = total
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
|
||||||
return this.videoService
|
return this.videoService
|
||||||
.getVideoChannelVideos(this.videoChannel, newPagination, this.sort)
|
.getVideoChannelVideos(this.videoChannel, newPagination, this.sort)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(({ totalVideos }) => {
|
tap(({ total }) => {
|
||||||
this.titlePage = this.i18n('Published {{totalVideos}} videos', { totalVideos })
|
this.titlePage = this.i18n('Published {{total}} videos', { total })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { fromEvent } from 'rxjs'
|
import { fromEvent } from 'rxjs'
|
||||||
import { ViewportScroller } from '@angular/common'
|
import { ViewportScroller } from '@angular/common'
|
||||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
|
@ -33,7 +34,8 @@ export class AppComponent implements OnInit {
|
||||||
private redirectService: RedirectService,
|
private redirectService: RedirectService,
|
||||||
private screenService: ScreenService,
|
private screenService: ScreenService,
|
||||||
private hotkeysService: HotkeysService,
|
private hotkeysService: HotkeysService,
|
||||||
private themeService: ThemeService
|
private themeService: ThemeService,
|
||||||
|
private hooks: HooksService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get serverVersion () {
|
get serverVersion () {
|
||||||
|
@ -206,7 +208,7 @@ export class AppComponent implements OnInit {
|
||||||
|
|
||||||
await this.pluginService.loadPluginsByScope('common')
|
await this.pluginService.loadPluginsByScope('common')
|
||||||
|
|
||||||
this.pluginService.runHook('action:application.loaded')
|
this.hooks.runAction('action:application.init')
|
||||||
}
|
}
|
||||||
|
|
||||||
private initHotkeys () {
|
private initHotkeys () {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { UserNotificationSocket } from '@app/core/notification/user-notification
|
||||||
import { ServerConfigResolver } from './routing/server-config-resolver.service'
|
import { ServerConfigResolver } from './routing/server-config-resolver.service'
|
||||||
import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
|
import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
|
||||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -63,6 +64,7 @@ import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
UnloggedGuard,
|
UnloggedGuard,
|
||||||
|
|
||||||
PluginService,
|
PluginService,
|
||||||
|
HooksService,
|
||||||
|
|
||||||
RedirectService,
|
RedirectService,
|
||||||
Notifier,
|
Notifier,
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
|
import { ClientActionHookName, ClientFilterHookName } from '@shared/models/plugins/client-hook.model'
|
||||||
|
import { from, Observable } from 'rxjs'
|
||||||
|
import { mergeMap, switchMap } from 'rxjs/operators'
|
||||||
|
import { ServerService } from '@app/core/server'
|
||||||
|
import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
|
||||||
|
|
||||||
|
type RawFunction<U, T> = (params: U) => T
|
||||||
|
type ObservableFunction<U, T> = RawFunction<U, Observable<T>>
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HooksService {
|
||||||
|
constructor (
|
||||||
|
private server: ServerService,
|
||||||
|
private pluginService: PluginService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
wrapObject<T, U extends ClientFilterHookName> (result: T, hookName: U) {
|
||||||
|
return this.pluginService.runHook(hookName, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapObsFun
|
||||||
|
<P, R, H1 extends ClientFilterHookName, H2 extends ClientFilterHookName>
|
||||||
|
(fun: ObservableFunction<P, R>, params: P, scope: PluginClientScope, hookParamName: H1, hookResultName: H2) {
|
||||||
|
return from(this.pluginService.ensurePluginsAreLoaded(scope))
|
||||||
|
.pipe(
|
||||||
|
mergeMap(() => this.wrapObject(params, hookParamName)),
|
||||||
|
switchMap(params => fun(params)),
|
||||||
|
mergeMap(result => this.pluginService.runHook(hookResultName, result, params))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async wrapFun<U, T, V extends ClientFilterHookName> (fun: RawFunction<U, T>, params: U, hookName: V) {
|
||||||
|
const result = fun(params)
|
||||||
|
|
||||||
|
return this.pluginService.runHook(hookName, result, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
runAction<T, U extends ClientActionHookName> (hookName: U, params?: T) {
|
||||||
|
this.pluginService.runHook(hookName, params)
|
||||||
|
.catch((err: any) => console.error('Fatal hook error.', { err }))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,13 @@ import { Router } from '@angular/router'
|
||||||
import { ServerConfigPlugin } from '@shared/models'
|
import { ServerConfigPlugin } from '@shared/models'
|
||||||
import { ServerService } from '@app/core/server/server.service'
|
import { ServerService } from '@app/core/server/server.service'
|
||||||
import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
|
import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
|
||||||
import { PluginScope } from '@shared/models/plugins/plugin-scope.type'
|
|
||||||
import { environment } from '../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model'
|
import { RegisterHookOptions } from '@shared/models/plugins/register-hook.model'
|
||||||
import { ReplaySubject } from 'rxjs'
|
import { ReplaySubject } from 'rxjs'
|
||||||
import { first, shareReplay } from 'rxjs/operators'
|
import { first, shareReplay } from 'rxjs/operators'
|
||||||
|
import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
|
||||||
|
import { ClientHook, ClientHookName } from '@shared/models/plugins/client-hook.model'
|
||||||
|
import { PluginClientScope } from '@shared/models/plugins/plugin-client-scope.type'
|
||||||
|
|
||||||
interface HookStructValue extends RegisterHookOptions {
|
interface HookStructValue extends RegisterHookOptions {
|
||||||
plugin: ServerConfigPlugin
|
plugin: ServerConfigPlugin
|
||||||
|
@ -21,14 +23,18 @@ type PluginInfo = {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PluginService {
|
export class PluginService implements ClientHook {
|
||||||
pluginsLoaded = new ReplaySubject<boolean>(1)
|
pluginsBuilt = new ReplaySubject<boolean>(1)
|
||||||
|
|
||||||
|
pluginsLoaded: { [ scope in PluginClientScope ]: ReplaySubject<boolean> } = {
|
||||||
|
common: new ReplaySubject<boolean>(1),
|
||||||
|
'video-watch': new ReplaySubject<boolean>(1)
|
||||||
|
}
|
||||||
|
|
||||||
private plugins: ServerConfigPlugin[] = []
|
private plugins: ServerConfigPlugin[] = []
|
||||||
private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
|
private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
|
||||||
private loadedPlugins: { [ name: string ]: boolean } = {}
|
|
||||||
private loadedScripts: { [ script: string ]: boolean } = {}
|
private loadedScripts: { [ script: string ]: boolean } = {}
|
||||||
private loadedScopes: PluginScope[] = []
|
private loadedScopes: PluginClientScope[] = []
|
||||||
|
|
||||||
private hooks: { [ name: string ]: HookStructValue[] } = {}
|
private hooks: { [ name: string ]: HookStructValue[] } = {}
|
||||||
|
|
||||||
|
@ -45,12 +51,18 @@ export class PluginService {
|
||||||
|
|
||||||
this.buildScopeStruct()
|
this.buildScopeStruct()
|
||||||
|
|
||||||
this.pluginsLoaded.next(true)
|
this.pluginsBuilt.next(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ensurePluginsAreLoaded () {
|
ensurePluginsAreBuilt () {
|
||||||
return this.pluginsLoaded.asObservable()
|
return this.pluginsBuilt.asObservable()
|
||||||
|
.pipe(first(), shareReplay())
|
||||||
|
.toPromise()
|
||||||
|
}
|
||||||
|
|
||||||
|
ensurePluginsAreLoaded (scope: PluginClientScope) {
|
||||||
|
return this.pluginsLoaded[scope].asObservable()
|
||||||
.pipe(first(), shareReplay())
|
.pipe(first(), shareReplay())
|
||||||
.toPromise()
|
.toPromise()
|
||||||
}
|
}
|
||||||
|
@ -90,9 +102,9 @@ export class PluginService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPluginsByScope (scope: PluginScope, isReload = false) {
|
async loadPluginsByScope (scope: PluginClientScope, isReload = false) {
|
||||||
try {
|
try {
|
||||||
await this.ensurePluginsAreLoaded()
|
await this.ensurePluginsAreBuilt()
|
||||||
|
|
||||||
if (!isReload) this.loadedScopes.push(scope)
|
if (!isReload) this.loadedScopes.push(scope)
|
||||||
|
|
||||||
|
@ -111,32 +123,24 @@ export class PluginService {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
this.pluginsLoaded[scope].next(true)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Cannot load plugins by scope %s.', scope, err)
|
console.error('Cannot load plugins by scope %s.', scope, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async runHook (hookName: string, param?: any) {
|
async runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T> {
|
||||||
let result = param
|
if (!this.hooks[hookName]) return Promise.resolve(result)
|
||||||
|
|
||||||
if (!this.hooks[hookName]) return result
|
const hookType = getHookType(hookName)
|
||||||
|
|
||||||
const wait = hookName.startsWith('static:')
|
|
||||||
|
|
||||||
for (const hook of this.hooks[hookName]) {
|
for (const hook of this.hooks[hookName]) {
|
||||||
try {
|
console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
|
||||||
const p = hook.handler(param)
|
|
||||||
|
|
||||||
if (wait) {
|
result = await internalRunHook(hook.handler, hookType, result, params, err => {
|
||||||
result = await p
|
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
|
||||||
} else if (p.catch) {
|
})
|
||||||
p.catch((err: Error) => {
|
|
||||||
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.plugin, hook.clientScript, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AdvancedSearch } from '@app/search/advanced-search.model'
|
||||||
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
|
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
|
||||||
import { immutableAssign } from '@app/shared/misc/utils'
|
import { immutableAssign } from '@app/shared/misc/utils'
|
||||||
import { Video } from '@app/shared/video/video.model'
|
import { Video } from '@app/shared/video/video.model'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-search',
|
selector: 'my-search',
|
||||||
|
@ -41,7 +42,8 @@ export class SearchComponent implements OnInit, OnDestroy {
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private authService: AuthService
|
private authService: AuthService,
|
||||||
|
private hooks: HooksService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get user () {
|
get user () {
|
||||||
|
@ -93,18 +95,18 @@ export class SearchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
search () {
|
search () {
|
||||||
forkJoin([
|
forkJoin([
|
||||||
this.searchService.searchVideos(this.currentSearch, this.pagination, this.advancedSearch),
|
this.getVideosObs(),
|
||||||
this.searchService.searchVideoChannels(this.currentSearch, immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }))
|
this.getVideoChannelObs()
|
||||||
])
|
])
|
||||||
.subscribe(
|
.subscribe(
|
||||||
([ videosResult, videoChannelsResult ]) => {
|
([ videosResult, videoChannelsResult ]) => {
|
||||||
this.results = this.results
|
this.results = this.results
|
||||||
.concat(videoChannelsResult.data)
|
.concat(videoChannelsResult.data)
|
||||||
.concat(videosResult.videos)
|
.concat(videosResult.data)
|
||||||
this.pagination.totalItems = videosResult.totalVideos + videoChannelsResult.total
|
this.pagination.totalItems = videosResult.total + videoChannelsResult.total
|
||||||
|
|
||||||
// Focus on channels if there are no enough videos
|
// Focus on channels if there are no enough videos
|
||||||
if (this.firstSearch === true && videosResult.videos.length < this.pagination.itemsPerPage) {
|
if (this.firstSearch === true && videosResult.data.length < this.pagination.itemsPerPage) {
|
||||||
this.resetPagination()
|
this.resetPagination()
|
||||||
this.firstSearch = false
|
this.firstSearch = false
|
||||||
|
|
||||||
|
@ -117,7 +119,6 @@ export class SearchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
err => this.notifier.error(err.message)
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onNearOfBottom () {
|
onNearOfBottom () {
|
||||||
|
@ -163,4 +164,35 @@ export class SearchComponent implements OnInit, OnDestroy {
|
||||||
queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search })
|
queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getVideosObs () {
|
||||||
|
const params = {
|
||||||
|
search: this.currentSearch,
|
||||||
|
componentPagination: this.pagination,
|
||||||
|
advancedSearch: this.advancedSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hooks.wrapObsFun(
|
||||||
|
this.searchService.searchVideos.bind(this.searchService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.search.videos.list.params',
|
||||||
|
'filter:api.search.videos.list.result'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getVideoChannelObs () {
|
||||||
|
const params = {
|
||||||
|
search: this.currentSearch,
|
||||||
|
componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage })
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hooks.wrapObsFun(
|
||||||
|
this.searchService.searchVideoChannels.bind(this.searchService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.search.video-channels.list.params',
|
||||||
|
'filter:api.search.video-channels.list.result'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,14 @@ export class SearchService {
|
||||||
private videoService: VideoService
|
private videoService: VideoService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
searchVideos (
|
searchVideos (parameters: {
|
||||||
search: string,
|
search: string,
|
||||||
componentPagination: ComponentPagination,
|
componentPagination: ComponentPagination,
|
||||||
advancedSearch: AdvancedSearch
|
advancedSearch: AdvancedSearch
|
||||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
}): Observable<ResultList<Video>> {
|
||||||
const url = SearchService.BASE_SEARCH_URL + 'videos'
|
const { search, componentPagination, advancedSearch } = parameters
|
||||||
|
|
||||||
|
const url = SearchService.BASE_SEARCH_URL + 'videos'
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -48,12 +49,13 @@ export class SearchService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchVideoChannels (
|
searchVideoChannels (parameters: {
|
||||||
search: string,
|
search: string,
|
||||||
componentPagination: ComponentPagination
|
componentPagination: ComponentPagination
|
||||||
): Observable<{ data: VideoChannel[], total: number }> {
|
}): Observable<ResultList<VideoChannel>> {
|
||||||
const url = SearchService.BASE_SEARCH_URL + 'video-channels'
|
const { search, componentPagination } = parameters
|
||||||
|
|
||||||
|
const url = SearchService.BASE_SEARCH_URL + 'video-channels'
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class OverviewService {
|
||||||
of(object.videos)
|
of(object.videos)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })),
|
switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })),
|
||||||
map(result => result.videos),
|
map(result => result.data),
|
||||||
tap(videos => {
|
tap(videos => {
|
||||||
videosOverviewResult[key].push(immutableAssign(object, { videos }))
|
videosOverviewResult[key].push(immutableAssign(object, { videos }))
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { Notifier, ServerService } from '@app/core'
|
||||||
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
|
import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
|
||||||
|
import { ResultList } from '@shared/models'
|
||||||
|
|
||||||
enum GroupDate {
|
enum GroupDate {
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
|
@ -73,7 +74,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
||||||
private groupedDateLabels: { [id in GroupDate]: string }
|
private groupedDateLabels: { [id in GroupDate]: string }
|
||||||
private groupedDates: { [id: number]: GroupDate } = {}
|
private groupedDates: { [id: number]: GroupDate } = {}
|
||||||
|
|
||||||
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
|
abstract getVideosObservable (page: number): Observable<ResultList<Video>>
|
||||||
|
|
||||||
abstract generateSyndicationList (): void
|
abstract generateSyndicationList (): void
|
||||||
|
|
||||||
|
@ -138,12 +139,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMoreVideos () {
|
loadMoreVideos () {
|
||||||
const observable = this.getVideosObservable(this.pagination.currentPage)
|
this.getVideosObservable(this.pagination.currentPage).subscribe(
|
||||||
|
({ data, total }) => {
|
||||||
observable.subscribe(
|
this.pagination.totalItems = total
|
||||||
({ videos, totalVideos }) => {
|
this.videos = this.videos.concat(data)
|
||||||
this.pagination.totalItems = totalVideos
|
|
||||||
this.videos = this.videos.concat(videos)
|
|
||||||
|
|
||||||
if (this.groupByDate) this.buildGroupedDateLabels()
|
if (this.groupByDate) this.buildGroupedDateLabels()
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export interface VideosProvider {
|
||||||
filter?: VideoFilter,
|
filter?: VideoFilter,
|
||||||
categoryOneOf?: number,
|
categoryOneOf?: number,
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
}): Observable<{ videos: Video[], totalVideos: number }>
|
}): Observable<ResultList<Video>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -65,11 +65,11 @@ export class VideoService implements VideosProvider {
|
||||||
return VideoService.BASE_VIDEO_URL + uuid + '/watching'
|
return VideoService.BASE_VIDEO_URL + uuid + '/watching'
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideo (uuid: string): Observable<VideoDetails> {
|
getVideo (options: { videoId: string }): Observable<VideoDetails> {
|
||||||
return this.serverService.localeObservable
|
return this.serverService.localeObservable
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(translations => {
|
switchMap(translations => {
|
||||||
return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
|
return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId)
|
||||||
.pipe(map(videoHash => ({ videoHash, translations })))
|
.pipe(map(videoHash => ({ videoHash, translations })))
|
||||||
}),
|
}),
|
||||||
map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
|
map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
|
||||||
|
@ -123,7 +123,7 @@ export class VideoService implements VideosProvider {
|
||||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number }> {
|
getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<ResultList<Video>> {
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -141,7 +141,7 @@ export class VideoService implements VideosProvider {
|
||||||
account: Account,
|
account: Account,
|
||||||
videoPagination: ComponentPagination,
|
videoPagination: ComponentPagination,
|
||||||
sort: VideoSortField
|
sort: VideoSortField
|
||||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
): Observable<ResultList<Video>> {
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -159,7 +159,7 @@ export class VideoService implements VideosProvider {
|
||||||
videoChannel: VideoChannel,
|
videoChannel: VideoChannel,
|
||||||
videoPagination: ComponentPagination,
|
videoPagination: ComponentPagination,
|
||||||
sort: VideoSortField
|
sort: VideoSortField
|
||||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
): Observable<ResultList<Video>> {
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -176,7 +176,7 @@ export class VideoService implements VideosProvider {
|
||||||
getPlaylistVideos (
|
getPlaylistVideos (
|
||||||
videoPlaylistId: number | string,
|
videoPlaylistId: number | string,
|
||||||
videoPagination: ComponentPagination
|
videoPagination: ComponentPagination
|
||||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
): Observable<ResultList<Video>> {
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -190,10 +190,11 @@ export class VideoService implements VideosProvider {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserSubscriptionVideos (
|
getUserSubscriptionVideos (parameters: {
|
||||||
videoPagination: ComponentPagination,
|
videoPagination: ComponentPagination,
|
||||||
sort: VideoSortField
|
sort: VideoSortField
|
||||||
): Observable<{ videos: Video[], totalVideos: number }> {
|
}): Observable<ResultList<Video>> {
|
||||||
|
const { videoPagination, sort } = parameters
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -213,7 +214,7 @@ export class VideoService implements VideosProvider {
|
||||||
filter?: VideoFilter,
|
filter?: VideoFilter,
|
||||||
categoryOneOf?: number,
|
categoryOneOf?: number,
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
}): Observable<{ videos: Video[], totalVideos: number }> {
|
}): Observable<ResultList<Video>> {
|
||||||
const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters
|
const { videoPagination, sort, filter, categoryOneOf, languageOneOf } = parameters
|
||||||
|
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
|
||||||
|
@ -344,7 +345,7 @@ export class VideoService implements VideosProvider {
|
||||||
videos.push(new Video(videoJson, translations))
|
videos.push(new Video(videoJson, translations))
|
||||||
}
|
}
|
||||||
|
|
||||||
return { videos, totalVideos }
|
return { total: totalVideos, data: videos }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template
|
||||||
import { VideoSortField } from '@app/shared/video/sort-field.type'
|
import { VideoSortField } from '@app/shared/video/sort-field.type'
|
||||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
|
import { ResultList } from '@shared/models'
|
||||||
|
|
||||||
export type SelectionType = { [ id: number ]: boolean }
|
export type SelectionType = { [ id: number ]: boolean }
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
|
||||||
@Input() pagination: ComponentPagination
|
@Input() pagination: ComponentPagination
|
||||||
@Input() titlePage: string
|
@Input() titlePage: string
|
||||||
@Input() miniatureDisplayOptions: MiniatureDisplayOptions
|
@Input() miniatureDisplayOptions: MiniatureDisplayOptions
|
||||||
@Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<{ videos: Video[], totalVideos: number }>
|
@Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>>
|
||||||
@ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
|
@ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective>
|
||||||
|
|
||||||
@Output() selectionChange = new EventEmitter<SelectionType>()
|
@Output() selectionChange = new EventEmitter<SelectionType>()
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class VideoUpdateResolver implements Resolve<any> {
|
||||||
resolve (route: ActivatedRouteSnapshot) {
|
resolve (route: ActivatedRouteSnapshot) {
|
||||||
const uuid: string = route.params[ 'uuid' ]
|
const uuid: string = route.params[ 'uuid' ]
|
||||||
|
|
||||||
return this.videoService.getVideo(uuid)
|
return this.videoService.getVideo({ videoId: uuid })
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(video => {
|
switchMap(video => {
|
||||||
return forkJoin([
|
return forkJoin([
|
||||||
|
|
|
@ -48,11 +48,13 @@ export class VideoCommentService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoCommentThreads (
|
getVideoCommentThreads (parameters: {
|
||||||
videoId: number | string,
|
videoId: number | string,
|
||||||
componentPagination: ComponentPagination,
|
componentPagination: ComponentPagination,
|
||||||
sort: VideoSortField
|
sort: VideoSortField
|
||||||
): Observable<{ comments: VideoComment[], totalComments: number}> {
|
}): Observable<{ comments: VideoComment[], totalComments: number}> {
|
||||||
|
const { videoId, componentPagination, sort } = parameters
|
||||||
|
|
||||||
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
|
@ -67,7 +69,11 @@ export class VideoCommentService {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoThreadComments (videoId: number | string, threadId: number): Observable<VideoCommentThreadTree> {
|
getVideoThreadComments (parameters: {
|
||||||
|
videoId: number | string,
|
||||||
|
threadId: number
|
||||||
|
}): Observable<VideoCommentThreadTree> {
|
||||||
|
const { videoId, threadId } = parameters
|
||||||
const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}`
|
const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}`
|
||||||
|
|
||||||
return this.authHttp
|
return this.authHttp
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { VideoComment } from './video-comment.model'
|
||||||
import { VideoCommentService } from './video-comment.service'
|
import { VideoCommentService } from './video-comment.service'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { Syndication } from '@app/shared/video/syndication.model'
|
import { Syndication } from '@app/shared/video/syndication.model'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-comments',
|
selector: 'my-video-comments',
|
||||||
|
@ -45,7 +46,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
private videoCommentService: VideoCommentService,
|
private videoCommentService: VideoCommentService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private i18n: I18n
|
private i18n: I18n,
|
||||||
|
private hooks: HooksService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
@ -73,8 +75,20 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
viewReplies (commentId: number, highlightThread = false) {
|
viewReplies (commentId: number, highlightThread = false) {
|
||||||
this.threadLoading[commentId] = true
|
this.threadLoading[commentId] = true
|
||||||
|
|
||||||
this.videoCommentService.getVideoThreadComments(this.video.id, commentId)
|
const params = {
|
||||||
.subscribe(
|
videoId: this.video.id,
|
||||||
|
threadId: commentId
|
||||||
|
}
|
||||||
|
|
||||||
|
const obs = this.hooks.wrapObsFun(
|
||||||
|
this.videoCommentService.getVideoThreadComments.bind(this.videoCommentService),
|
||||||
|
params,
|
||||||
|
'video-watch',
|
||||||
|
'filter:api.video-watch.video-thread-replies.list.params',
|
||||||
|
'filter:api.video-watch.video-thread-replies.list.result'
|
||||||
|
)
|
||||||
|
|
||||||
|
obs.subscribe(
|
||||||
res => {
|
res => {
|
||||||
this.threadComments[commentId] = res
|
this.threadComments[commentId] = res
|
||||||
this.threadLoading[commentId] = false
|
this.threadLoading[commentId] = false
|
||||||
|
@ -91,16 +105,29 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMoreComments () {
|
loadMoreThreads () {
|
||||||
this.videoCommentService.getVideoCommentThreads(this.video.id, this.componentPagination, this.sort)
|
const params = {
|
||||||
.subscribe(
|
videoId: this.video.id,
|
||||||
res => {
|
componentPagination: this.componentPagination,
|
||||||
this.comments = this.comments.concat(res.comments)
|
sort: this.sort
|
||||||
this.componentPagination.totalItems = res.totalComments
|
}
|
||||||
},
|
|
||||||
|
|
||||||
err => this.notifier.error(err.message)
|
const obs = this.hooks.wrapObsFun(
|
||||||
)
|
this.videoCommentService.getVideoCommentThreads.bind(this.videoCommentService),
|
||||||
|
params,
|
||||||
|
'video-watch',
|
||||||
|
'filter:api.video-watch.video-threads.list.params',
|
||||||
|
'filter:api.video-watch.video-threads.list.result'
|
||||||
|
)
|
||||||
|
|
||||||
|
obs.subscribe(
|
||||||
|
res => {
|
||||||
|
this.comments = this.comments.concat(res.comments)
|
||||||
|
this.componentPagination.totalItems = res.totalComments
|
||||||
|
},
|
||||||
|
|
||||||
|
err => this.notifier.error(err.message)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommentThreadCreated (comment: VideoComment) {
|
onCommentThreadCreated (comment: VideoComment) {
|
||||||
|
@ -169,7 +196,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.componentPagination.currentPage++
|
this.componentPagination.currentPage++
|
||||||
|
|
||||||
if (hasMoreItems(this.componentPagination)) {
|
if (hasMoreItems(this.componentPagination)) {
|
||||||
this.loadMoreComments()
|
this.loadMoreThreads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +224,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid)
|
this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid)
|
||||||
|
|
||||||
this.loadMoreComments()
|
this.loadMoreThreads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,11 +66,11 @@ export class VideoWatchPlaylistComponent {
|
||||||
|
|
||||||
loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) {
|
loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) {
|
||||||
this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination)
|
this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination)
|
||||||
.subscribe(({ totalVideos, videos }) => {
|
.subscribe(({ total, data }) => {
|
||||||
this.playlistVideos = this.playlistVideos.concat(videos)
|
this.playlistVideos = this.playlistVideos.concat(data)
|
||||||
this.playlistPagination.totalItems = totalVideos
|
this.playlistPagination.totalItems = total
|
||||||
|
|
||||||
if (totalVideos === 0) {
|
if (total === 0) {
|
||||||
this.noPlaylistVideos = true
|
this.noPlaylistVideos = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils'
|
||||||
import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
|
import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
|
||||||
import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
|
import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
|
||||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-watch',
|
selector: 'my-video-watch',
|
||||||
|
@ -93,6 +94,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
private videoCaptionService: VideoCaptionService,
|
private videoCaptionService: VideoCaptionService,
|
||||||
private i18n: I18n,
|
private i18n: I18n,
|
||||||
private hotkeysService: HotkeysService,
|
private hotkeysService: HotkeysService,
|
||||||
|
private hooks: HooksService,
|
||||||
@Inject(LOCALE_ID) private localeId: string
|
@Inject(LOCALE_ID) private localeId: string
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -131,7 +133,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.theaterEnabled = getStoredTheater()
|
this.theaterEnabled = getStoredTheater()
|
||||||
|
|
||||||
this.pluginService.runHook('action:video-watch.loaded')
|
this.hooks.runAction('action:video-watch.init')
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
@ -246,9 +248,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (this.player) this.player.pause()
|
if (this.player) this.player.pause()
|
||||||
|
|
||||||
|
const videoObs = this.hooks.wrapObsFun(
|
||||||
|
this.videoService.getVideo.bind(this.videoService),
|
||||||
|
{ videoId },
|
||||||
|
'video-watch',
|
||||||
|
'filter:api.video-watch.video.get.params',
|
||||||
|
'filter:api.video-watch.video.get.result'
|
||||||
|
)
|
||||||
|
|
||||||
// Video did change
|
// Video did change
|
||||||
forkJoin(
|
forkJoin(
|
||||||
this.videoService.getVideo(videoId),
|
videoObs,
|
||||||
this.videoCaptionService.listCaptions(videoId)
|
this.videoCaptionService.listCaptions(videoId)
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@ -486,6 +496,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.setOpenGraphTags()
|
this.setOpenGraphTags()
|
||||||
this.checkUserRating()
|
this.checkUserRating()
|
||||||
|
|
||||||
|
this.hooks.runAction('action:video-watch.video.loaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRating (nextRating: UserVideoRateType) {
|
private setRating (nextRating: UserVideoRateType) {
|
||||||
|
|
|
@ -33,20 +33,24 @@ export class RecentVideosRecommendationService implements RecommendationService
|
||||||
private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> {
|
private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> {
|
||||||
const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 }
|
const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 }
|
||||||
const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' })
|
const defaultSubscription = this.videos.getVideos({ videoPagination: pagination, sort: '-createdAt' })
|
||||||
.pipe(map(v => v.videos))
|
.pipe(map(v => v.data))
|
||||||
|
|
||||||
if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription
|
if (!recommendation.tags || recommendation.tags.length === 0) return defaultSubscription
|
||||||
|
|
||||||
return this.searchService.searchVideos('',
|
const params = {
|
||||||
pagination,
|
search: '',
|
||||||
new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' })
|
componentPagination: pagination,
|
||||||
).pipe(
|
advancedSearch: new AdvancedSearch({ tagsOneOf: recommendation.tags.join(','), sort: '-createdAt' })
|
||||||
map(v => v.videos),
|
}
|
||||||
switchMap(videos => {
|
|
||||||
if (videos.length <= 1) return defaultSubscription
|
|
||||||
|
|
||||||
return of(videos)
|
return this.searchService.searchVideos(params)
|
||||||
})
|
.pipe(
|
||||||
)
|
map(v => v.data),
|
||||||
|
switchMap(videos => {
|
||||||
|
if (videos.length <= 1) return defaultSubscription
|
||||||
|
|
||||||
|
return of(videos)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
import { UserRight } from '../../../../../shared/models/users'
|
import { UserRight } from '../../../../../shared/models/users'
|
||||||
import { Notifier, ServerService } from '@app/core'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-local',
|
selector: 'my-videos-local',
|
||||||
|
@ -31,7 +32,8 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
|
||||||
protected notifier: Notifier,
|
protected notifier: Notifier,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected screenService: ScreenService,
|
protected screenService: ScreenService,
|
||||||
private videoService: VideoService
|
private videoService: VideoService,
|
||||||
|
private hooks: HooksService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@ -55,14 +57,21 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
|
||||||
|
|
||||||
getVideosObservable (page: number) {
|
getVideosObservable (page: number) {
|
||||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
const params = {
|
||||||
return this.videoService.getVideos({
|
|
||||||
videoPagination: newPagination,
|
videoPagination: newPagination,
|
||||||
sort: this.sort,
|
sort: this.sort,
|
||||||
filter: this.filter,
|
filter: this.filter,
|
||||||
categoryOneOf: this.categoryOneOf,
|
categoryOneOf: this.categoryOneOf,
|
||||||
languageOneOf: this.languageOneOf
|
languageOneOf: this.languageOneOf
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return this.hooks.wrapObsFun(
|
||||||
|
this.videoService.getVideos.bind(this.videoService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.videos.list.local.params',
|
||||||
|
'filter:api.videos.list.local.result'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSyndicationList () {
|
generateSyndicationList () {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
import { Notifier, ServerService } from '@app/core'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-recently-added',
|
selector: 'my-videos-recently-added',
|
||||||
|
@ -29,7 +30,8 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
|
||||||
protected notifier: Notifier,
|
protected notifier: Notifier,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected screenService: ScreenService,
|
protected screenService: ScreenService,
|
||||||
private videoService: VideoService
|
private videoService: VideoService,
|
||||||
|
private hooks: HooksService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@ -48,14 +50,20 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
|
||||||
|
|
||||||
getVideosObservable (page: number) {
|
getVideosObservable (page: number) {
|
||||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
const params = {
|
||||||
return this.videoService.getVideos({
|
|
||||||
videoPagination: newPagination,
|
videoPagination: newPagination,
|
||||||
sort: this.sort,
|
sort: this.sort,
|
||||||
filter: undefined,
|
|
||||||
categoryOneOf: this.categoryOneOf,
|
categoryOneOf: this.categoryOneOf,
|
||||||
languageOneOf: this.languageOneOf
|
languageOneOf: this.languageOneOf
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return this.hooks.wrapObsFun(
|
||||||
|
this.videoService.getVideos.bind(this.videoService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.videos.list.recently-added.params',
|
||||||
|
'filter:api.videos.list.recently-added.result'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSyndicationList () {
|
generateSyndicationList () {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
import { Notifier, ServerService } from '@app/core'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-trending',
|
selector: 'my-videos-trending',
|
||||||
|
@ -28,7 +29,8 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
|
||||||
protected notifier: Notifier,
|
protected notifier: Notifier,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected screenService: ScreenService,
|
protected screenService: ScreenService,
|
||||||
private videoService: VideoService
|
private videoService: VideoService,
|
||||||
|
private hooks: HooksService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
@ -61,13 +63,20 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
|
||||||
|
|
||||||
getVideosObservable (page: number) {
|
getVideosObservable (page: number) {
|
||||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
return this.videoService.getVideos({
|
const params = {
|
||||||
videoPagination: newPagination,
|
videoPagination: newPagination,
|
||||||
sort: this.sort,
|
sort: this.sort,
|
||||||
filter: undefined,
|
|
||||||
categoryOneOf: this.categoryOneOf,
|
categoryOneOf: this.categoryOneOf,
|
||||||
languageOneOf: this.languageOneOf
|
languageOneOf: this.languageOneOf
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return this.hooks.wrapObsFun(
|
||||||
|
this.videoService.getVideos.bind(this.videoService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.videos.list.trending.params',
|
||||||
|
'filter:api.videos.list.trending.result'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSyndicationList () {
|
generateSyndicationList () {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
|
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
|
||||||
import { Notifier, ServerService } from '@app/core'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-user-subscriptions',
|
selector: 'my-videos-user-subscriptions',
|
||||||
|
@ -29,7 +30,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
||||||
protected notifier: Notifier,
|
protected notifier: Notifier,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected screenService: ScreenService,
|
protected screenService: ScreenService,
|
||||||
private videoService: VideoService
|
private videoService: VideoService,
|
||||||
|
private hooks: HooksService
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@ -46,8 +48,18 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
||||||
|
|
||||||
getVideosObservable (page: number) {
|
getVideosObservable (page: number) {
|
||||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
const params = {
|
||||||
|
videoPagination: newPagination,
|
||||||
|
sort: this.sort
|
||||||
|
}
|
||||||
|
|
||||||
return this.videoService.getUserSubscriptionVideos(newPagination, this.sort)
|
return this.hooks.wrapObsFun(
|
||||||
|
this.videoService.getUserSubscriptionVideos.bind(this.videoService),
|
||||||
|
params,
|
||||||
|
'common',
|
||||||
|
'filter:api.videos.list.user-subscriptions.params',
|
||||||
|
'filter:api.videos.list.user-subscriptions.result'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSyndicationList () {
|
generateSyndicationList () {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"suppressImplicitAnyIndexErrors":true,
|
"suppressImplicitAnyIndexErrors":true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"node_modules/@types"
|
"node_modules/@types"
|
||||||
|
|
|
@ -29,7 +29,7 @@ async function internalRunHook <T> (handler: Function, hookType: HookType, resul
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hookType === HookType.ACTION) {
|
if (hookType === HookType.ACTION) {
|
||||||
if (isCatchable(p)) p.catch(err => onError(err))
|
if (isCatchable(p)) p.catch((err: any) => onError(err))
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
export type ClientFilterHookName =
|
||||||
|
'filter:api.videos.list.trending.params' |
|
||||||
|
'filter:api.videos.list.trending.result' |
|
||||||
|
|
||||||
|
'filter:api.videos.list.local.params' |
|
||||||
|
'filter:api.videos.list.local.result' |
|
||||||
|
|
||||||
|
'filter:api.videos.list.recently-added.params' |
|
||||||
|
'filter:api.videos.list.recently-added.result' |
|
||||||
|
|
||||||
|
'filter:api.videos.list.user-subscriptions.params' |
|
||||||
|
'filter:api.videos.list.user-subscriptions.result' |
|
||||||
|
|
||||||
|
'filter:api.video-watch.video.get.params' |
|
||||||
|
'filter:api.video-watch.video.get.result' |
|
||||||
|
|
||||||
|
'filter:api.video-watch.video-threads.list.params' |
|
||||||
|
'filter:api.video-watch.video-threads.list.result' |
|
||||||
|
|
||||||
|
'filter:api.video-watch.video-thread-replies.list.params' |
|
||||||
|
'filter:api.video-watch.video-thread-replies.list.result' |
|
||||||
|
|
||||||
|
'filter:api.search.videos.list.params' |
|
||||||
|
'filter:api.search.videos.list.result' |
|
||||||
|
'filter:api.search.video-channels.list.params' |
|
||||||
|
'filter:api.search.video-channels.list.result'
|
||||||
|
|
||||||
|
export type ClientActionHookName =
|
||||||
|
'action:application.init' |
|
||||||
|
|
||||||
|
'action:video-watch.init' |
|
||||||
|
|
||||||
|
'action:video-watch.video.loaded'
|
||||||
|
|
||||||
|
export type ClientHookName = ClientActionHookName | ClientFilterHookName
|
||||||
|
|
||||||
|
export interface ClientHook {
|
||||||
|
runHook <T> (hookName: ClientHookName, result?: T, params?: any): Promise<T>
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export type PluginClientScope = 'common' | 'video-watch'
|
|
@ -1 +0,0 @@
|
||||||
export type PluginScope = 'common' | 'video-watch'
|
|
|
@ -30,5 +30,5 @@ export type ServerActionHookName =
|
||||||
export type ServerHookName = ServerFilterHookName | ServerActionHookName
|
export type ServerHookName = ServerFilterHookName | ServerActionHookName
|
||||||
|
|
||||||
export interface ServerHook {
|
export interface ServerHook {
|
||||||
runHook (hookName: ServerHookName, params?: any)
|
runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||||
import { ClientScript } from '../plugins/plugin-package-json.model'
|
import { ClientScript } from '../plugins/plugin-package-json.model'
|
||||||
|
import { PluginClientScope } from '../plugins/plugin-scope.type'
|
||||||
|
|
||||||
export interface ServerConfigPlugin {
|
export interface ServerConfigPlugin {
|
||||||
name: string
|
name: string
|
||||||
version: string
|
version: string
|
||||||
description: string
|
description: string
|
||||||
clientScripts: { [name: string]: ClientScript }
|
clientScripts: { [name in PluginClientScope]: ClientScript }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerConfigTheme extends ServerConfigPlugin {
|
export interface ServerConfigTheme extends ServerConfigPlugin {
|
||||||
|
|
Loading…
Reference in New Issue