]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/shared/shared-forms/advanced-input-filter.component.ts
Support ICU in TS components
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-forms / advanced-input-filter.component.ts
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'
7
8 export type AdvancedInputFilter = {
9 title: string
10
11 children: AdvancedInputFilterChild[]
12 }
13
14 export type AdvancedInputFilterChild = {
15 label: string
16 value: string
17 }
18
19 const logger = debug('peertube:AdvancedInputFilterComponent')
20
21 @Component({
22 selector: 'my-advanced-input-filter',
23 templateUrl: './advanced-input-filter.component.html',
24 styleUrls: [ './advanced-input-filter.component.scss' ]
25 })
26 export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
27 @Input() filters: AdvancedInputFilter[] = []
28 @Input() emitOnInit = true
29
30 @Output() search = new EventEmitter<string>()
31
32 searchValue: string
33
34 private enabledFilters = new Set<string>()
35
36 private searchStream: Subject<string>
37
38 private viewInitialized = false
39 private emitSearchAfterViewInit = false
40
41 constructor (
42 private route: ActivatedRoute,
43 private restService: RestService,
44 private router: Router
45 ) { }
46
47 ngOnInit () {
48 this.initSearchStream()
49 this.listenToRouteSearchChange()
50 }
51
52 ngAfterViewInit () {
53 this.viewInitialized = true
54
55 // Init after view init to not send an event too early
56 if (this.emitOnInit && this.emitSearchAfterViewInit) this.emitSearch()
57 }
58
59 onInputSearch (event: Event) {
60 this.scheduleSearchUpdate((event.target as HTMLInputElement).value)
61 }
62
63 onResetTableFilter () {
64 this.immediateSearchUpdate('')
65 }
66
67 hasFilters () {
68 return this.filters && this.filters.length !== 0
69 }
70
71 isFilterEnabled (filter: AdvancedInputFilterChild) {
72 return this.enabledFilters.has(filter.value)
73 }
74
75 onFilterClick (filter: AdvancedInputFilterChild) {
76 const newSearch = this.isFilterEnabled(filter)
77 ? this.removeFilterToSearch(this.searchValue, filter)
78 : this.addFilterToSearch(this.searchValue, filter)
79
80 this.router.navigate([ '.' ], { relativeTo: this.route, queryParams: { search: newSearch.trim() } })
81 }
82
83 private scheduleSearchUpdate (value: string) {
84 this.searchValue = value
85 this.searchStream.next(this.searchValue)
86 }
87
88 private immediateSearchUpdate (value: string) {
89 this.searchValue = value
90
91 this.setQueryParams(this.searchValue)
92 this.parseFilters(this.searchValue)
93 this.emitSearch()
94 }
95
96 private listenToRouteSearchChange () {
97 this.route.queryParams
98 .subscribe(params => {
99 const search = params.search || ''
100
101 logger('On route search change "%s".', search)
102
103 if (this.searchValue === search) return
104
105 this.searchValue = search
106
107 this.parseFilters(this.searchValue)
108
109 this.emitSearch()
110 })
111 }
112
113 private initSearchStream () {
114 this.searchStream = new Subject()
115
116 this.searchStream
117 .pipe(
118 debounceTime(300),
119 distinctUntilChanged()
120 )
121 .subscribe(() => {
122 this.setQueryParams(this.searchValue)
123 this.parseFilters(this.searchValue)
124
125 this.emitSearch()
126 })
127 }
128
129 private emitSearch () {
130 if (!this.viewInitialized) {
131 this.emitSearchAfterViewInit = true
132 return
133 }
134
135 logger('On search "%s".', this.searchValue)
136
137 this.search.emit(this.searchValue)
138 }
139
140 private setQueryParams (search: string) {
141 const queryParams: Params = {}
142
143 if (search) Object.assign(queryParams, { search })
144 this.router.navigate([ ], { queryParams })
145 }
146
147 private removeFilterToSearch (search: string, removedFilter: AdvancedInputFilterChild) {
148 return search.replace(removedFilter.value, '')
149 }
150
151 private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) {
152 const prefix = newFilter.value.split(':').shift()
153
154 // Tokenize search and remove a potential existing filter
155 const tokens = this.restService.tokenizeString(search)
156 .filter(t => !t.startsWith(prefix))
157
158 tokens.push(newFilter.value)
159
160 return tokens.join(' ')
161 }
162
163 private parseFilters (search: string) {
164 const tokens = this.restService.tokenizeString(search)
165
166 this.enabledFilters = new Set()
167
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)
172 }
173 }
174 }
175 }
176 }