1 import * as debug from 'debug'
2 import { Subject } from 'rxjs'
3 import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
4 import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
5 import { ActivatedRoute, Params, Router } from '@angular/router'
6 import { RestService } from '@app/core'
8 export type AdvancedInputFilter = {
11 children: AdvancedInputFilterChild[]
14 export type AdvancedInputFilterChild = {
19 const debugLogger = debug('peertube:AdvancedInputFilterComponent')
22 selector: 'my-advanced-input-filter',
23 templateUrl: './advanced-input-filter.component.html',
24 styleUrls: [ './advanced-input-filter.component.scss' ]
26 export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
27 @Input() filters: AdvancedInputFilter[] = []
28 @Input() emitOnInit = true
30 @Output() search = new EventEmitter<string>()
34 private enabledFilters = new Set<string>()
36 private searchStream: Subject<string>
38 private viewInitialized = false
39 private emitSearchAfterViewInit = false
42 private route: ActivatedRoute,
43 private restService: RestService,
44 private router: Router
48 this.initSearchStream()
49 this.listenToRouteSearchChange()
53 this.viewInitialized = true
55 // Init after view init to not send an event too early
56 if (this.emitOnInit && this.emitSearchAfterViewInit) this.emitSearch()
59 onInputSearch (event: Event) {
60 this.scheduleSearchUpdate((event.target as HTMLInputElement).value)
63 onResetTableFilter () {
64 this.immediateSearchUpdate('')
68 return this.filters && this.filters.length !== 0
71 isFilterEnabled (filter: AdvancedInputFilterChild) {
72 return this.enabledFilters.has(filter.value)
75 onFilterClick (filter: AdvancedInputFilterChild) {
76 const newSearch = this.isFilterEnabled(filter)
77 ? this.removeFilterToSearch(this.searchValue, filter)
78 : this.addFilterToSearch(this.searchValue, filter)
80 this.router.navigate([ '.' ], { relativeTo: this.route, queryParams: { search: newSearch.trim() } })
83 private scheduleSearchUpdate (value: string) {
84 this.searchValue = value
85 this.searchStream.next(this.searchValue)
88 private immediateSearchUpdate (value: string) {
89 this.searchValue = value
91 this.setQueryParams(this.searchValue)
92 this.parseFilters(this.searchValue)
96 private listenToRouteSearchChange () {
97 this.route.queryParams
98 .subscribe(params => {
99 const search = params.search || ''
101 debugLogger('On route search change "%s".', search)
103 if (this.searchValue === search) return
105 this.searchValue = search
107 this.parseFilters(this.searchValue)
113 private initSearchStream () {
114 this.searchStream = new Subject()
119 distinctUntilChanged()
122 this.setQueryParams(this.searchValue)
123 this.parseFilters(this.searchValue)
129 private emitSearch () {
130 if (!this.viewInitialized) {
131 this.emitSearchAfterViewInit = true
135 debugLogger('On search "%s".', this.searchValue)
137 this.search.emit(this.searchValue)
140 private setQueryParams (search: string) {
141 const queryParams: Params = {}
143 if (search) Object.assign(queryParams, { search })
144 this.router.navigate([ ], { queryParams })
147 private removeFilterToSearch (search: string, removedFilter: AdvancedInputFilterChild) {
148 return search.replace(removedFilter.value, '')
151 private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) {
152 const prefix = newFilter.value.split(':').shift()
154 // Tokenize search and remove a potential existing filter
155 const tokens = this.restService.tokenizeString(search)
156 .filter(t => !t.startsWith(prefix))
158 tokens.push(newFilter.value)
160 return tokens.join(' ')
163 private parseFilters (search: string) {
164 const tokens = this.restService.tokenizeString(search)
166 this.enabledFilters = new Set()
168 for (const group of this.filters) {
169 for (const filter of group.children) {
170 if (tokens.includes(filter.value)) {
171 this.enabledFilters.add(filter.value)