Improve advanced input filter

This commit is contained in:
Chocobozzz 2021-11-03 14:23:55 +01:00
parent d324756edb
commit dd6d2a7ce5
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
14 changed files with 131 additions and 33 deletions

View File

@ -30,11 +30,11 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
title: $localize`Advanced filters`, title: $localize`Advanced filters`,
children: [ children: [
{ {
queryParams: { search: 'type:auto' }, value: 'type:auto',
label: $localize`Automatic blocks` label: $localize`Automatic blocks`
}, },
{ {
queryParams: { search: 'type:manual' }, value: 'type:manual',
label: $localize`Manual blocks` label: $localize`Manual blocks`
} }
] ]

View File

@ -47,11 +47,11 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
title: $localize`Advanced filters`, title: $localize`Advanced filters`,
children: [ children: [
{ {
queryParams: { search: 'local:true' }, value: 'local:true',
label: $localize`Local comments` label: $localize`Local comments`
}, },
{ {
queryParams: { search: 'local:false' }, value: 'local:false',
label: $localize`Remote comments` label: $localize`Remote comments`
} }
] ]

View File

@ -39,7 +39,7 @@ export class UserListComponent extends RestTable implements OnInit {
title: $localize`Advanced filters`, title: $localize`Advanced filters`,
children: [ children: [
{ {
queryParams: { search: 'banned:true' }, value: 'banned:true',
label: $localize`Banned users` label: $localize`Banned users`
} }
] ]

View File

@ -44,11 +44,11 @@ export class VideoAdminService {
title: $localize`Video type`, title: $localize`Video type`,
children: [ children: [
{ {
queryParams: { search: 'isLive:false' }, value: 'isLive:false',
label: $localize`VOD` label: $localize`VOD`
}, },
{ {
queryParams: { search: 'isLive:true' }, value: 'isLive:true',
label: $localize`Live` label: $localize`Live`
} }
] ]
@ -58,19 +58,19 @@ export class VideoAdminService {
title: $localize`Video files`, title: $localize`Video files`,
children: [ children: [
{ {
queryParams: { search: 'webtorrent:true' }, value: 'webtorrent:true',
label: $localize`With WebTorrent` label: $localize`With WebTorrent`
}, },
{ {
queryParams: { search: 'webtorrent:false' }, value: 'webtorrent:false',
label: $localize`Without WebTorrent` label: $localize`Without WebTorrent`
}, },
{ {
queryParams: { search: 'hls:true' }, value: 'hls:true',
label: $localize`With HLS` label: $localize`With HLS`
}, },
{ {
queryParams: { search: 'hls:false' }, value: 'hls:false',
label: $localize`Without HLS` label: $localize`Without HLS`
} }
] ]
@ -80,11 +80,11 @@ export class VideoAdminService {
title: $localize`Videos scope`, title: $localize`Videos scope`,
children: [ children: [
{ {
queryParams: { search: 'isLocal:false' }, value: 'isLocal:false',
label: $localize`Remote videos` label: $localize`Remote videos`
}, },
{ {
queryParams: { search: 'isLocal:true' }, value: 'isLocal:true',
label: $localize`Local videos` label: $localize`Local videos`
} }
] ]
@ -94,7 +94,7 @@ export class VideoAdminService {
title: $localize`Exclude`, title: $localize`Exclude`,
children: [ children: [
{ {
queryParams: { search: 'excludeMuted' }, value: 'excludeMuted',
label: $localize`Exclude muted accounts` label: $localize`Exclude muted accounts`
} }
] ]

View File

@ -86,7 +86,7 @@ export class VideoListComponent extends RestTable implements OnInit {
} }
getPrivacyBadgeClass (video: Video) { getPrivacyBadgeClass (video: Video) {
if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-blue' if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green'
return 'badge-yellow' return 'badge-yellow'
} }

View File

@ -39,7 +39,7 @@ export class MyFollowersComponent implements OnInit {
this.auth.userInformationLoaded.subscribe(() => { this.auth.userInformationLoaded.subscribe(() => {
const channelFilters = this.auth.getUser().videoChannels.map(c => { const channelFilters = this.auth.getUser().videoChannels.map(c => {
return { return {
queryParams: { search: 'channel:' + c.name }, value: 'channel:' + c.name,
label: c.name label: c.name
} }
}) })

View File

@ -82,7 +82,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
const channelFilters = this.userChannels.map(c => { const channelFilters = this.userChannels.map(c => {
return { return {
queryParams: { search: 'channel:' + c.name }, value: 'channel:' + c.name,
label: c.name label: c.name
} }
}) })
@ -92,7 +92,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
title: $localize`Advanced filters`, title: $localize`Advanced filters`,
children: [ children: [
{ {
queryParams: { search: 'isLive:true' }, value: 'isLive:true',
label: $localize`Only live videos` label: $localize`Only live videos`
} }
] ]

View File

@ -82,9 +82,7 @@ export class RestService {
parseQueryStringFilter <T extends QueryStringFilterPrefixes> (q: string, prefixes: T): ParseQueryStringFiltersResult<keyof T> { parseQueryStringFilter <T extends QueryStringFilterPrefixes> (q: string, prefixes: T): ParseQueryStringFiltersResult<keyof T> {
if (!q) return {} if (!q) return {}
// Tokenize the strings using spaces that are not in quotes const tokens = this.tokenizeString(q)
const tokens = q.match(/(?:[^\s"]+|"[^"]*")+/g)
.filter(token => !!token)
// Build prefix array // Build prefix array
const prefixeStrings = Object.values(prefixes) const prefixeStrings = Object.values(prefixes)
@ -137,4 +135,12 @@ export class RestService {
...additionalFilters ...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)
}
} }

View File

@ -39,23 +39,23 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
title: $localize`Advanced filters`, title: $localize`Advanced filters`,
children: [ children: [
{ {
queryParams: { search: 'state:pending' }, value: 'state:pending',
label: $localize`Unsolved reports` label: $localize`Unsolved reports`
}, },
{ {
queryParams: { search: 'state:accepted' }, value: 'state:accepted',
label: $localize`Accepted reports` label: $localize`Accepted reports`
}, },
{ {
queryParams: { search: 'state:rejected' }, value: 'state:rejected',
label: $localize`Refused reports` label: $localize`Refused reports`
}, },
{ {
queryParams: { search: 'videoIs:blacklisted' }, value: 'videoIs:blacklisted',
label: $localize`Reports with blocked videos` label: $localize`Reports with blocked videos`
}, },
{ {
queryParams: { search: 'videoIs:deleted' }, value: 'videoIs:deleted',
label: $localize`Reports with deleted videos` label: $localize`Reports with deleted videos`
} }
] ]

View File

@ -8,9 +8,11 @@
<ng-container *ngFor="let group of filters"> <ng-container *ngFor="let group of filters">
<h6 class="dropdown-header">{{ group.title }}</h6> <h6 class="dropdown-header">{{ group.title }}</h6>
<a *ngFor="let filter of group.children" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item"> <button *ngFor="let filter of group.children" (click)="onFilterClick(filter)" class="dropdown-item">
<my-global-icon [ngClass]="{ 'no-visible': !isFilterEnabled(filter) }" iconName="tick"></my-global-icon>
{{ filter.label }} {{ filter.label }}
</a> </button>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@ -1,6 +1,10 @@
@use '_variables' as *; @use '_variables' as *;
@use '_mixins' as *; @use '_mixins' as *;
.dropdown-item {
font-size: 14px;
}
input { input {
@include peertube-input-text(250px); @include peertube-input-text(250px);
} }
@ -8,3 +12,22 @@ input {
.input-group-text { .input-group-text {
background-color: transparent; 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;
}
}

View File

@ -3,14 +3,17 @@ import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators' import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { ActivatedRoute, Params, Router } from '@angular/router' import { ActivatedRoute, Params, Router } from '@angular/router'
import { RestService } from '@app/core'
export type AdvancedInputFilter = { export type AdvancedInputFilter = {
title: string title: string
children: { children: AdvancedInputFilterChild[]
}
export type AdvancedInputFilterChild = {
label: string label: string
queryParams: Params value: string
}[]
} }
const logger = debug('peertube:AdvancedInputFilterComponent') const logger = debug('peertube:AdvancedInputFilterComponent')
@ -28,6 +31,8 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
searchValue: string searchValue: string
private enabledFilters = new Set<string>()
private searchStream: Subject<string> private searchStream: Subject<string>
private viewInitialized = false private viewInitialized = false
@ -35,6 +40,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
constructor ( constructor (
private route: ActivatedRoute, private route: ActivatedRoute,
private restService: RestService,
private router: Router private router: Router
) { } ) { }
@ -62,6 +68,18 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
return this.filters && this.filters.length !== 0 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) { private scheduleSearchUpdate (value: string) {
this.searchValue = value this.searchValue = value
this.searchStream.next(this.searchValue) this.searchStream.next(this.searchValue)
@ -71,6 +89,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
this.searchValue = value this.searchValue = value
this.setQueryParams(this.searchValue) this.setQueryParams(this.searchValue)
this.parseFilters(this.searchValue)
this.emitSearch() this.emitSearch()
} }
@ -84,6 +103,9 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
if (this.searchValue === search) return if (this.searchValue === search) return
this.searchValue = search this.searchValue = search
this.parseFilters(this.searchValue)
this.emitSearch() this.emitSearch()
}) })
} }
@ -98,6 +120,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
) )
.subscribe(() => { .subscribe(() => {
this.setQueryParams(this.searchValue) this.setQueryParams(this.searchValue)
this.parseFilters(this.searchValue)
this.emitSearch() this.emitSearch()
}) })
@ -120,4 +143,34 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
if (search) Object.assign(queryParams, { search }) if (search) Object.assign(queryParams, { search })
this.router.navigate([ ], { queryParams }) 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)
}
}
}
}
} }

View File

@ -0,0 +1,13 @@
function findCommonElement <T> (array1: T[], array2: T[]) {
for (const a of array1) {
for (const b of array2) {
if (a === b) return a
}
}
return null
}
export {
findCommonElement
}

View File

@ -1 +1,2 @@
export * from './array'
export * from './object' export * from './object'