From dd6d2a7ce50e7ff02e00995ccc87f8f929ad9709 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 3 Nov 2021 14:23:55 +0100 Subject: [PATCH] Improve advanced input filter --- .../video-block-list.component.ts | 4 +- .../video-comment-list.component.ts | 4 +- .../users/user-list/user-list.component.ts | 2 +- .../overview/videos/video-admin.service.ts | 18 +++--- .../overview/videos/video-list.component.ts | 2 +- .../my-follows/my-followers.component.ts | 2 +- .../my-videos/my-videos.component.ts | 4 +- client/src/app/core/rest/rest.service.ts | 14 +++-- .../abuse-list-table.component.ts | 10 +-- .../advanced-input-filter.component.html | 6 +- .../advanced-input-filter.component.scss | 23 +++++++ .../advanced-input-filter.component.ts | 61 +++++++++++++++++-- shared/core-utils/utils/array.ts | 13 ++++ shared/core-utils/utils/index.ts | 1 + 14 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 shared/core-utils/utils/array.ts diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 1fe8d0f9d..dca746f4e 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts @@ -30,11 +30,11 @@ export class VideoBlockListComponent extends RestTable implements OnInit { title: $localize`Advanced filters`, children: [ { - queryParams: { search: 'type:auto' }, + value: 'type:auto', label: $localize`Automatic blocks` }, { - queryParams: { search: 'type:manual' }, + value: 'type:manual', label: $localize`Manual blocks` } ] diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts index a60b228af..25fe65133 100644 --- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts +++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts @@ -47,11 +47,11 @@ export class VideoCommentListComponent extends RestTable implements OnInit { title: $localize`Advanced filters`, children: [ { - queryParams: { search: 'local:true' }, + value: 'local:true', label: $localize`Local comments` }, { - queryParams: { search: 'local:false' }, + value: 'local:false', label: $localize`Remote comments` } ] diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index 548e6e80f..9fba11cbd 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts @@ -39,7 +39,7 @@ export class UserListComponent extends RestTable implements OnInit { title: $localize`Advanced filters`, children: [ { - queryParams: { search: 'banned:true' }, + value: 'banned:true', label: $localize`Banned users` } ] diff --git a/client/src/app/+admin/overview/videos/video-admin.service.ts b/client/src/app/+admin/overview/videos/video-admin.service.ts index b90fe22d8..f80de7acd 100644 --- a/client/src/app/+admin/overview/videos/video-admin.service.ts +++ b/client/src/app/+admin/overview/videos/video-admin.service.ts @@ -44,11 +44,11 @@ export class VideoAdminService { title: $localize`Video type`, children: [ { - queryParams: { search: 'isLive:false' }, + value: 'isLive:false', label: $localize`VOD` }, { - queryParams: { search: 'isLive:true' }, + value: 'isLive:true', label: $localize`Live` } ] @@ -58,19 +58,19 @@ export class VideoAdminService { title: $localize`Video files`, children: [ { - queryParams: { search: 'webtorrent:true' }, + value: 'webtorrent:true', label: $localize`With WebTorrent` }, { - queryParams: { search: 'webtorrent:false' }, + value: 'webtorrent:false', label: $localize`Without WebTorrent` }, { - queryParams: { search: 'hls:true' }, + value: 'hls:true', label: $localize`With HLS` }, { - queryParams: { search: 'hls:false' }, + value: 'hls:false', label: $localize`Without HLS` } ] @@ -80,11 +80,11 @@ export class VideoAdminService { title: $localize`Videos scope`, children: [ { - queryParams: { search: 'isLocal:false' }, + value: 'isLocal:false', label: $localize`Remote videos` }, { - queryParams: { search: 'isLocal:true' }, + value: 'isLocal:true', label: $localize`Local videos` } ] @@ -94,7 +94,7 @@ export class VideoAdminService { title: $localize`Exclude`, children: [ { - queryParams: { search: 'excludeMuted' }, + value: 'excludeMuted', label: $localize`Exclude muted accounts` } ] diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts index 635552cf5..0f98a5d33 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts @@ -86,7 +86,7 @@ export class VideoListComponent extends RestTable implements OnInit { } getPrivacyBadgeClass (video: Video) { - if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-blue' + if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green' return 'badge-yellow' } diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts index 4a72b983f..0dd9bf6f5 100644 --- a/client/src/app/+my-library/my-follows/my-followers.component.ts +++ b/client/src/app/+my-library/my-follows/my-followers.component.ts @@ -39,7 +39,7 @@ export class MyFollowersComponent implements OnInit { this.auth.userInformationLoaded.subscribe(() => { const channelFilters = this.auth.getUser().videoChannels.map(c => { return { - queryParams: { search: 'channel:' + c.name }, + value: 'channel:' + c.name, label: c.name } }) diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index a117d0915..261e87f99 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts @@ -82,7 +82,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { const channelFilters = this.userChannels.map(c => { return { - queryParams: { search: 'channel:' + c.name }, + value: 'channel:' + c.name, label: c.name } }) @@ -92,7 +92,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { title: $localize`Advanced filters`, children: [ { - queryParams: { search: 'isLive:true' }, + value: 'isLive:true', label: $localize`Only live videos` } ] diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts index 59152e658..b2a5a3f72 100644 --- a/client/src/app/core/rest/rest.service.ts +++ b/client/src/app/core/rest/rest.service.ts @@ -82,13 +82,11 @@ export class RestService { parseQueryStringFilter (q: string, prefixes: T): ParseQueryStringFiltersResult { if (!q) return {} - // Tokenize the strings using spaces that are not in quotes - const tokens = q.match(/(?:[^\s"]+|"[^"]*")+/g) - .filter(token => !!token) + const tokens = this.tokenizeString(q) // Build prefix array const prefixeStrings = Object.values(prefixes) - .map(p => p.prefix) + .map(p => p.prefix) logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`) @@ -137,4 +135,12 @@ export class RestService { ...additionalFilters } } + + tokenizeString (q: string) { + if (!q) return [] + + // Tokenize the strings using spaces that are not in quotes + return q.match(/(?:[^\s"]+|"[^"]*")+/g) + .filter(token => !!token) + } } diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index 10f5861b9..b902726fa 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts @@ -39,23 +39,23 @@ export class AbuseListTableComponent extends RestTable implements OnInit { title: $localize`Advanced filters`, children: [ { - queryParams: { search: 'state:pending' }, + value: 'state:pending', label: $localize`Unsolved reports` }, { - queryParams: { search: 'state:accepted' }, + value: 'state:accepted', label: $localize`Accepted reports` }, { - queryParams: { search: 'state:rejected' }, + value: 'state:rejected', label: $localize`Refused reports` }, { - queryParams: { search: 'videoIs:blacklisted' }, + value: 'videoIs:blacklisted', label: $localize`Reports with blocked videos` }, { - queryParams: { search: 'videoIs:deleted' }, + value: 'videoIs:deleted', label: $localize`Reports with deleted videos` } ] diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.html b/client/src/app/shared/shared-forms/advanced-input-filter.component.html index c662b9bb6..7031cb53b 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.html +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html @@ -8,9 +8,11 @@ - + diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss index 07a43761c..ee1b3b508 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss @@ -1,6 +1,10 @@ @use '_variables' as *; @use '_mixins' as *; +.dropdown-item { + font-size: 14px; +} + input { @include peertube-input-text(250px); } @@ -8,3 +12,22 @@ input { .input-group-text { background-color: transparent; } + +my-global-icon { + $size: 18px; + $margin: 2px; + + @include margin-right($margin); + + opacity: 1; + width: 18px; + height: 18px; + + + &.no-visible { + @include margin-right($size + $margin); + + width: 0; + height: 0; + } +} diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts index a12dddf7a..d8aeaa0fa 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts @@ -3,14 +3,17 @@ import { Subject } from 'rxjs' import { debounceTime, distinctUntilChanged } from 'rxjs/operators' import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { ActivatedRoute, Params, Router } from '@angular/router' +import { RestService } from '@app/core' export type AdvancedInputFilter = { title: string - children: { - label: string - queryParams: Params - }[] + children: AdvancedInputFilterChild[] +} + +export type AdvancedInputFilterChild = { + label: string + value: string } const logger = debug('peertube:AdvancedInputFilterComponent') @@ -28,6 +31,8 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { searchValue: string + private enabledFilters = new Set() + private searchStream: Subject private viewInitialized = false @@ -35,6 +40,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { constructor ( private route: ActivatedRoute, + private restService: RestService, private router: Router ) { } @@ -62,6 +68,18 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { return this.filters && this.filters.length !== 0 } + isFilterEnabled (filter: AdvancedInputFilterChild) { + return this.enabledFilters.has(filter.value) + } + + onFilterClick (filter: AdvancedInputFilterChild) { + const newSearch = this.isFilterEnabled(filter) + ? this.removeFilterToSearch(this.searchValue, filter) + : this.addFilterToSearch(this.searchValue, filter) + + this.router.navigate([ '.' ], { relativeTo: this.route, queryParams: { search: newSearch.trim() } }) + } + private scheduleSearchUpdate (value: string) { this.searchValue = value this.searchStream.next(this.searchValue) @@ -71,6 +89,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { this.searchValue = value this.setQueryParams(this.searchValue) + this.parseFilters(this.searchValue) this.emitSearch() } @@ -84,6 +103,9 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { if (this.searchValue === search) return this.searchValue = search + + this.parseFilters(this.searchValue) + this.emitSearch() }) } @@ -98,6 +120,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { ) .subscribe(() => { this.setQueryParams(this.searchValue) + this.parseFilters(this.searchValue) this.emitSearch() }) @@ -120,4 +143,34 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { if (search) Object.assign(queryParams, { search }) this.router.navigate([ ], { queryParams }) } + + private removeFilterToSearch (search: string, removedFilter: AdvancedInputFilterChild) { + return search.replace(removedFilter.value, '') + } + + private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) { + const prefix = newFilter.value.split(':').shift() + + // Tokenize search and remove a potential existing filter + const tokens = this.restService.tokenizeString(search) + .filter(t => !t.startsWith(prefix)) + + tokens.push(newFilter.value) + + return tokens.join(' ') + } + + private parseFilters (search: string) { + const tokens = this.restService.tokenizeString(search) + + this.enabledFilters = new Set() + + for (const group of this.filters) { + for (const filter of group.children) { + if (tokens.includes(filter.value)) { + this.enabledFilters.add(filter.value) + } + } + } + } } diff --git a/shared/core-utils/utils/array.ts b/shared/core-utils/utils/array.ts new file mode 100644 index 000000000..9e326a5aa --- /dev/null +++ b/shared/core-utils/utils/array.ts @@ -0,0 +1,13 @@ +function findCommonElement (array1: T[], array2: T[]) { + for (const a of array1) { + for (const b of array2) { + if (a === b) return a + } + } + + return null +} + +export { + findCommonElement +} diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts index a71977d88..8d16365a8 100644 --- a/shared/core-utils/utils/index.ts +++ b/shared/core-utils/utils/index.ts @@ -1 +1,2 @@ +export * from './array' export * from './object' -- 2.41.0