]>
Commit | Line | Data |
---|---|---|
2e46eb97 C |
1 | import * as debug from 'debug' |
2 | import { Subject } from 'rxjs' | |
3 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | |
f676e0e3 | 4 | import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2e46eb97 | 5 | import { ActivatedRoute, Params, Router } from '@angular/router' |
dd6d2a7c | 6 | import { RestService } from '@app/core' |
1fd61899 C |
7 | |
8 | export type AdvancedInputFilter = { | |
978c87e7 C |
9 | title: string |
10 | ||
dd6d2a7c C |
11 | children: AdvancedInputFilterChild[] |
12 | } | |
13 | ||
14 | export type AdvancedInputFilterChild = { | |
15 | label: string | |
16 | value: string | |
1fd61899 C |
17 | } |
18 | ||
2e46eb97 C |
19 | const logger = debug('peertube:AdvancedInputFilterComponent') |
20 | ||
1fd61899 C |
21 | @Component({ |
22 | selector: 'my-advanced-input-filter', | |
23 | templateUrl: './advanced-input-filter.component.html', | |
24 | styleUrls: [ './advanced-input-filter.component.scss' ] | |
25 | }) | |
f676e0e3 | 26 | export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { |
1fd61899 | 27 | @Input() filters: AdvancedInputFilter[] = [] |
dd24f1bb | 28 | @Input() emitOnInit = true |
1fd61899 | 29 | |
2e46eb97 C |
30 | @Output() search = new EventEmitter<string>() |
31 | ||
32 | searchValue: string | |
33 | ||
dd6d2a7c C |
34 | private enabledFilters = new Set<string>() |
35 | ||
2e46eb97 C |
36 | private searchStream: Subject<string> |
37 | ||
f676e0e3 C |
38 | private viewInitialized = false |
39 | private emitSearchAfterViewInit = false | |
40 | ||
2e46eb97 C |
41 | constructor ( |
42 | private route: ActivatedRoute, | |
dd6d2a7c | 43 | private restService: RestService, |
2e46eb97 C |
44 | private router: Router |
45 | ) { } | |
46 | ||
47 | ngOnInit () { | |
48 | this.initSearchStream() | |
49 | this.listenToRouteSearchChange() | |
50 | } | |
1fd61899 | 51 | |
f676e0e3 C |
52 | ngAfterViewInit () { |
53 | this.viewInitialized = true | |
54 | ||
55 | // Init after view init to not send an event too early | |
dd24f1bb | 56 | if (this.emitOnInit && this.emitSearchAfterViewInit) this.emitSearch() |
f676e0e3 C |
57 | } |
58 | ||
2e46eb97 | 59 | onInputSearch (event: Event) { |
f676e0e3 | 60 | this.scheduleSearchUpdate((event.target as HTMLInputElement).value) |
1fd61899 C |
61 | } |
62 | ||
63 | onResetTableFilter () { | |
f676e0e3 | 64 | this.immediateSearchUpdate('') |
2e46eb97 C |
65 | } |
66 | ||
67 | hasFilters () { | |
7e76cc38 | 68 | return this.filters && this.filters.length !== 0 |
2e46eb97 C |
69 | } |
70 | ||
dd6d2a7c C |
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 | ||
f676e0e3 | 83 | private scheduleSearchUpdate (value: string) { |
2e46eb97 C |
84 | this.searchValue = value |
85 | this.searchStream.next(this.searchValue) | |
86 | } | |
87 | ||
f676e0e3 C |
88 | private immediateSearchUpdate (value: string) { |
89 | this.searchValue = value | |
90 | ||
91 | this.setQueryParams(this.searchValue) | |
dd6d2a7c | 92 | this.parseFilters(this.searchValue) |
f676e0e3 C |
93 | this.emitSearch() |
94 | } | |
95 | ||
2e46eb97 C |
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 | ||
4beda9e1 C |
103 | if (this.searchValue === search) return |
104 | ||
f676e0e3 | 105 | this.searchValue = search |
dd6d2a7c C |
106 | |
107 | this.parseFilters(this.searchValue) | |
108 | ||
f676e0e3 | 109 | this.emitSearch() |
2e46eb97 C |
110 | }) |
111 | } | |
112 | ||
113 | private initSearchStream () { | |
114 | this.searchStream = new Subject() | |
115 | ||
116 | this.searchStream | |
117 | .pipe( | |
f676e0e3 | 118 | debounceTime(300), |
2e46eb97 C |
119 | distinctUntilChanged() |
120 | ) | |
121 | .subscribe(() => { | |
2e46eb97 | 122 | this.setQueryParams(this.searchValue) |
dd6d2a7c | 123 | this.parseFilters(this.searchValue) |
f676e0e3 C |
124 | |
125 | this.emitSearch() | |
2e46eb97 C |
126 | }) |
127 | } | |
128 | ||
f676e0e3 C |
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 | ||
2e46eb97 C |
140 | private setQueryParams (search: string) { |
141 | const queryParams: Params = {} | |
142 | ||
143 | if (search) Object.assign(queryParams, { search }) | |
144 | this.router.navigate([ ], { queryParams }) | |
1fd61899 | 145 | } |
dd6d2a7c C |
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 | } | |
1fd61899 | 176 | } |