Improve advanced input filter
This commit is contained in:
parent
d324756edb
commit
dd6d2a7ce5
14 changed files with 131 additions and 33 deletions
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -82,13 +82,11 @@ export class RestService {
|
|||
parseQueryStringFilter <T extends QueryStringFilterPrefixes> (q: string, prefixes: T): ParseQueryStringFiltersResult<keyof T> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
<ng-container *ngFor="let group of filters">
|
||||
<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 }}
|
||||
</a>
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string>()
|
||||
|
||||
private searchStream: Subject<string>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
shared/core-utils/utils/array.ts
Normal file
13
shared/core-utils/utils/array.ts
Normal 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
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export * from './array'
|
||||
export * from './object'
|
||||
|
|
Loading…
Reference in a new issue