]>
Commit | Line | Data |
---|---|---|
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 debugLogger = 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 | debugLogger('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 | debugLogger('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 | } |