import { Subject } from 'rxjs' import { debounceTime, distinctUntilChanged } from 'rxjs/operators' import { Component, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' import { logger } from '@root-helpers/logger' import { PeerTubePluginIndex, PluginType } from '@shared/models' @Component({ selector: 'my-plugin-search', templateUrl: './plugin-search.component.html', styleUrls: [ './plugin-search.component.scss' ] }) export class PluginSearchComponent implements OnInit { pluginType: PluginType pagination: ComponentPagination = { currentPage: 1, itemsPerPage: 10, totalItems: null } sort = '-popularity' search = '' isSearching = false plugins: PeerTubePluginIndex[] = [] installing: { [name: string]: boolean } = {} pluginInstalled = false onDataSubject = new Subject() private searchSubject = new Subject() constructor ( private pluginService: PluginService, private pluginApiService: PluginApiService, private notifier: Notifier, private confirmService: ConfirmService, private router: Router, private route: ActivatedRoute ) { } ngOnInit () { if (!this.route.snapshot.queryParams['pluginType']) { const queryParams = { pluginType: PluginType.PLUGIN } this.router.navigate([], { queryParams }) } this.route.queryParams.subscribe(query => { if (!query['pluginType']) return this.pluginType = parseInt(query['pluginType'], 10) this.search = query['search'] || '' this.reloadPlugins() }) this.searchSubject.asObservable() .pipe( debounceTime(400), distinctUntilChanged() ) .subscribe(search => this.router.navigate([], { queryParams: { search }, queryParamsHandling: 'merge' })) } onSearchChange (event: Event) { const target = event.target as HTMLInputElement this.searchSubject.next(target.value) } reloadPlugins () { this.pagination.currentPage = 1 this.plugins = [] this.loadMorePlugins() } loadMorePlugins () { this.isSearching = true this.pluginApiService.searchAvailablePlugins(this.pluginType, this.pagination, this.sort, this.search) .subscribe({ next: res => { this.isSearching = false this.plugins = this.plugins.concat(res.data) this.pagination.totalItems = res.total this.onDataSubject.next(res.data) }, error: err => { logger.error(err) const message = $localize`The plugin index is not available. Please retry later.` this.notifier.error(message) } }) } onNearOfBottom () { if (!hasMoreItems(this.pagination)) return this.pagination.currentPage += 1 this.loadMorePlugins() } isInstalling (plugin: PeerTubePluginIndex) { return !!this.installing[plugin.npmName] } getShowRouterLink (plugin: PeerTubePluginIndex) { return [ '/admin', 'plugins', 'show', this.pluginService.nameToNpmName(plugin.name, this.pluginType) ] } isThemeSearch () { return this.pluginType === PluginType.THEME } async install (plugin: PeerTubePluginIndex) { if (this.installing[plugin.npmName]) return const res = await this.confirmService.confirm( $localize`Please only install plugins or themes you trust, since they can execute any code on your instance.`, $localize`Install ${plugin.name}?` ) if (res === false) return this.installing[plugin.npmName] = true this.pluginApiService.install(plugin.npmName) .subscribe({ next: () => { this.installing[plugin.npmName] = false this.pluginInstalled = true this.notifier.success($localize`${plugin.name} installed.`) plugin.installed = true }, error: err => { this.installing[plugin.npmName] = false this.notifier.error(err.message) } }) } }