From 6702a1b2ccd666285dee9c72b5bace641d2fce8b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 16 Jul 2019 11:33:22 +0200 Subject: Add ability to search available plugins --- .../plugin-list-installed.component.html | 4 +- .../plugin-list-installed.component.scss | 35 ------- .../plugin-list-installed.component.ts | 3 +- .../plugin-search/plugin-search.component.html | 55 ++++++++++ .../plugin-search/plugin-search.component.scss | 21 ++++ .../plugin-search/plugin-search.component.ts | 116 +++++++++++++++++++-- .../plugin-show-installed.component.ts | 3 +- .../+admin/plugins/shared/plugin-api.service.ts | 23 +++- .../plugins/shared/plugin-list.component.scss | 37 +++++++ .../src/app/shared/images/global-icon.component.ts | 1 + client/src/app/shared/misc/utils.ts | 18 ---- 11 files changed, 248 insertions(+), 68 deletions(-) create mode 100644 client/src/app/+admin/plugins/shared/plugin-list.component.scss (limited to 'client/src/app') diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html index f10b4eb8d..6d2155332 100644 --- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html +++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html @@ -2,7 +2,7 @@ -
+
{{ getNoResultMessage() }}
@@ -28,7 +28,7 @@ - diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss index 7641c507b..0b54ffda3 100644 --- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss +++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss @@ -1,41 +1,6 @@ @import '_variables'; @import '_mixins'; -.first-row { - margin-bottom: 10px; - - .plugin-name { - font-size: 16px; - margin-right: 10px; - font-weight: $font-semibold; - } - - .plugin-version { - opacity: 0.6; - } -} - -.second-row { - display: flex; - align-items: center; - justify-content: space-between; - - .description { - opacity: 0.8 - } - - .buttons { - > *:not(:last-child) { - margin-right: 10px; - } - } -} - -.action-button { - @include peertube-button-link; - @include button-with-icon(21px, 0, -2px); -} - .update-button[disabled="true"] /deep/ .action-button { cursor: default !important; } diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts index 67a11c3a8..9809759db 100644 --- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts +++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.ts @@ -6,13 +6,14 @@ import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pa import { ConfirmService, Notifier } from '@app/core' import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' import { ActivatedRoute, Router } from '@angular/router' -import { compareSemVer } from '@app/shared/misc/utils' +import { compareSemVer } from '@shared/core-utils/miscs/miscs' @Component({ selector: 'my-plugin-list-installed', templateUrl: './plugin-list-installed.component.html', styleUrls: [ '../shared/toggle-plugin-type.scss', + '../shared/plugin-list.component.scss', './plugin-list-installed.component.scss' ] }) diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html index e69de29bb..7dd103979 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html @@ -0,0 +1,55 @@ +
+ +
+ + + +
+ + + Popular + + + + + + + {{ pagination.totalItems }} {pagination.totalItems, plural, =1 {result} other {results}} for "{{ search }}" + + +
+ +
+ No results. +
+ +
+
+
+
+ {{ plugin.name }} + + {{ plugin.latestVersion }} +
+ +
+
{{ plugin.description }}
+ + +
+
+
+
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss index 5e6774739..ad6ff89da 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.scss @@ -1,2 +1,23 @@ @import '_variables'; @import '_mixins'; + +.search-bar { + display: flex; + justify-content: center; + margin: 30px 0; + + input { + @include peertube-input-text(60%); + height: 35px; + } +} + +.result-title { + font-size: 22px; + font-weight: 600; + margin-bottom: 15px; + + my-global-icon { + margin-right: 5px; + } +} diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts index 787be2c8c..935e11362 100644 --- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts +++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts @@ -1,33 +1,133 @@ -import { Component, OnInit, ViewChild } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { Notifier } from '@app/core' -import { SortMeta } from 'primeng/components/common/sortmeta' -import { ConfirmService, ServerService } from '../../../core' -import { RestPagination, RestTable, UserService } from '../../../shared' +import { ConfirmService } from '../../../core' import { I18n } from '@ngx-translate/i18n-polyfill' -import { User } from '../../../../../../shared' -import { UserBanModalComponent } from '@app/shared/moderation' -import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' import { PluginType } from '@shared/models/plugins/plugin.type' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' +import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' +import { ActivatedRoute, Router } from '@angular/router' +import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' +import { Subject } from 'rxjs' +import { debounceTime, distinctUntilChanged } from 'rxjs/operators' @Component({ selector: 'my-plugin-search', templateUrl: './plugin-search.component.html', styleUrls: [ '../shared/toggle-plugin-type.scss', + '../shared/plugin-list.component.scss', './plugin-search.component.scss' ] }) export class PluginSearchComponent implements OnInit { pluginTypeOptions: { label: string, value: PluginType }[] = [] + pluginType: PluginType = PluginType.PLUGIN + + pagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 10 + } + sort = '-popularity' + + search = '' + isSearching = false + + plugins: PeerTubePluginIndex[] = [] + installing: { [name: string]: boolean } = {} + + private searchSubject = new Subject() constructor ( private i18n: I18n, - private pluginService: PluginApiService + private pluginService: PluginApiService, + private notifier: Notifier, + private confirmService: ConfirmService, + private router: Router, + private route: ActivatedRoute ) { this.pluginTypeOptions = this.pluginService.getPluginTypeOptions() } ngOnInit () { + const query = this.route.snapshot.queryParams + if (query['pluginType']) this.pluginType = parseInt(query['pluginType'], 10) + + this.searchSubject.asObservable() + .pipe( + debounceTime(400), + distinctUntilChanged() + ) + .subscribe(search => { + this.search = search + this.reloadPlugins() + }) + + this.reloadPlugins() + } + + onSearchChange (search: string) { + this.searchSubject.next(search) + } + + reloadPlugins () { + this.pagination.currentPage = 1 + this.plugins = [] + + this.router.navigate([], { queryParams: { pluginType: this.pluginType } }) + + this.loadMorePlugins() + } + + loadMorePlugins () { + this.isSearching = true + + this.pluginService.searchAvailablePlugins(this.pluginType, this.pagination, this.sort, this.search) + .subscribe( + res => { + this.isSearching = false + + this.plugins = this.plugins.concat(res.data) + this.pagination.totalItems = res.total + }, + + err => this.notifier.error(err.message) + ) + } + + onNearOfBottom () { + if (!hasMoreItems(this.pagination)) return + + this.pagination.currentPage += 1 + + this.loadMorePlugins() + } + + isInstalling (plugin: PeerTubePluginIndex) { + return !!this.installing[plugin.npmName] + } + + async install (plugin: PeerTubePluginIndex) { + if (this.installing[plugin.npmName]) return + + const res = await this.confirmService.confirm( + this.i18n('Please only install plugins or themes you trust, since they can execute any code on your instance.'), + this.i18n('Install {{pluginName}}?', { pluginName: plugin.name }) + ) + if (res === false) return + + this.installing[plugin.npmName] = true + + this.pluginService.install(plugin.npmName) + .subscribe( + () => { + this.installing[plugin.npmName] = false + + this.notifier.success(this.i18n('{{pluginName}} installed.', { pluginName: plugin.name })) + + plugin.installed = true + }, + + err => this.notifier.error(err.message) + ) } } diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts index 8750bfd38..b99281a37 100644 --- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts +++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.ts @@ -7,7 +7,7 @@ import { ActivatedRoute } from '@angular/router' import { Subscription } from 'rxjs' import { map, switchMap } from 'rxjs/operators' import { RegisterSettingOptions } from '@shared/models/plugins/register-setting.model' -import { BuildFormArgument, BuildFormDefaultValues, FormReactive, FormValidatorService } from '@app/shared' +import { BuildFormArgument, FormReactive, FormValidatorService } from '@app/shared' @Component({ selector: 'my-plugin-show-installed', @@ -83,7 +83,6 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit } private buildSettingsForm () { - const defaultValues: BuildFormDefaultValues = {} const buildOptions: BuildFormArgument = {} const settingsValues: any = {} diff --git a/client/src/app/+admin/plugins/shared/plugin-api.service.ts b/client/src/app/+admin/plugins/shared/plugin-api.service.ts index 89f190675..51f086a93 100644 --- a/client/src/app/+admin/plugins/shared/plugin-api.service.ts +++ b/client/src/app/+admin/plugins/shared/plugin-api.service.ts @@ -11,6 +11,7 @@ import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model' import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model' import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model' import { RegisterSettingOptions } from '@shared/models/plugins/register-setting.model' +import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model' @Injectable() export class PluginApiService { @@ -45,7 +46,7 @@ export class PluginApiService { } getPlugins ( - type: PluginType, + pluginType: PluginType, componentPagination: ComponentPagination, sort: string ) { @@ -53,12 +54,30 @@ export class PluginApiService { let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) - params = params.append('type', type.toString()) + params = params.append('pluginType', pluginType.toString()) return this.authHttp.get>(PluginApiService.BASE_APPLICATION_URL, { params }) .pipe(catchError(res => this.restExtractor.handleError(res))) } + searchAvailablePlugins ( + pluginType: PluginType, + componentPagination: ComponentPagination, + sort: string, + search?: string + ) { + const pagination = this.restService.componentPaginationToRestPagination(componentPagination) + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + params = params.append('pluginType', pluginType.toString()) + + if (search) params = params.append('search', search) + + return this.authHttp.get>(PluginApiService.BASE_APPLICATION_URL + '/available', { params }) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + getPlugin (npmName: string) { const path = PluginApiService.BASE_APPLICATION_URL + '/' + npmName diff --git a/client/src/app/+admin/plugins/shared/plugin-list.component.scss b/client/src/app/+admin/plugins/shared/plugin-list.component.scss new file mode 100644 index 000000000..f250404ed --- /dev/null +++ b/client/src/app/+admin/plugins/shared/plugin-list.component.scss @@ -0,0 +1,37 @@ +@import '_variables'; +@import '_mixins'; + +.first-row { + margin-bottom: 10px; + + .plugin-name { + font-size: 16px; + margin-right: 10px; + font-weight: $font-semibold; + } + + .plugin-version { + opacity: 0.6; + } +} + +.second-row { + display: flex; + align-items: center; + justify-content: space-between; + + .description { + opacity: 0.8 + } + + .buttons { + > *:not(:last-child) { + margin-right: 10px; + } + } +} + +.action-button { + @include peertube-button-link; + @include button-with-icon(21px, 0, -2px); +} diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts index 5a3db4531..5b525dec1 100644 --- a/client/src/app/shared/images/global-icon.component.ts +++ b/client/src/app/shared/images/global-icon.component.ts @@ -45,6 +45,7 @@ const icons = { 'administration': require('../../../assets/images/menu/administration.html'), 'subscriptions': require('../../../assets/images/menu/subscriptions.html'), 'users': require('../../../assets/images/global/users.html'), + 'search': require('../../../assets/images/global/search.html'), 'refresh': require('../../../assets/images/global/refresh.html') } diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts index 098496d45..85fc1c3a0 100644 --- a/client/src/app/shared/misc/utils.ts +++ b/client/src/app/shared/misc/utils.ts @@ -134,23 +134,6 @@ function scrollToTop () { window.scroll(0, 0) } -// Thanks https://stackoverflow.com/a/16187766 -function compareSemVer (a: string, b: string) { - const regExStrip0 = /(\.0+)+$/ - const segmentsA = a.replace(regExStrip0, '').split('.') - const segmentsB = b.replace(regExStrip0, '').split('.') - - const l = Math.min(segmentsA.length, segmentsB.length) - - for (let i = 0; i < l; i++) { - const diff = parseInt(segmentsA[ i ], 10) - parseInt(segmentsB[ i ], 10) - - if (diff) return diff - } - - return segmentsA.length - segmentsB.length -} - export { sortBy, durationToString, @@ -161,7 +144,6 @@ export { getAbsoluteAPIUrl, dateToHuman, immutableAssign, - compareSemVer, objectToFormData, objectLineFeedToHtml, removeElementFromArray, -- cgit v1.2.3