]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/header/search-typeahead.component.ts
Improve remote runner config UX
[github/Chocobozzz/PeerTube.git] / client / src / app / header / search-typeahead.component.ts
CommitLineData
5fb2e288 1import { of } from 'rxjs'
67ed6552 2import { first, tap } from 'rxjs/operators'
5fb2e288 3import { ListKeyManager } from '@angular/cdk/a11y'
2989628b 4import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
4c1c1709 5import { ActivatedRoute, Params, Router } from '@angular/router'
9677fca7 6import { AuthService, ServerService } from '@app/core'
42b40636 7import { logger } from '@root-helpers/logger'
2989628b 8import { HTMLServerConfig, SearchTargetType } from '@shared/models'
5fb2e288 9import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
f409f0c3
RK
10
11@Component({
12 selector: 'my-search-typeahead',
13 templateUrl: './search-typeahead.component.html',
14 styleUrls: [ './search-typeahead.component.scss' ]
15})
2989628b 16export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDestroy {
5fb2e288 17 @ViewChildren(SuggestionComponent) suggestionItems: QueryList<SuggestionComponent>
f409f0c3
RK
18
19 hasChannel = false
20 inChannel = false
5fb2e288 21 areSuggestionsOpened = true
f409f0c3 22
9b8a7aa8 23 search = ''
2989628b 24 serverConfig: HTMLServerConfig
f409f0c3 25
f409f0c3
RK
26 inThisChannelText: string
27
6af662a5 28 keyboardEventsManager: ListKeyManager<SuggestionComponent>
5fb2e288
C
29 results: SuggestionPayload[] = []
30
31 activeSearch: SuggestionPayloadType
32
33 private scheduleKeyboardEventsInit = false
f409f0c3
RK
34
35 constructor (
36 private authService: AuthService,
37 private router: Router,
52cc0d54 38 private route: ActivatedRoute,
9b8a7aa8
RK
39 private serverService: ServerService
40 ) {}
f409f0c3
RK
41
42 ngOnInit () {
71e75ef2 43 this.route.queryParams
36004aa7 44 .pipe(first(params => this.isOnSearch() && params.search !== undefined && params.search !== null))
71e75ef2 45 .subscribe(params => this.search = params.search)
5fb2e288 46
2989628b
C
47 this.serverConfig = this.serverService.getHTMLConfig()
48 this.computeTypeahead()
49
50 this.serverService.configReloaded
5fb2e288
C
51 .subscribe(config => {
52 this.serverConfig = config
5fb2e288 53 this.computeTypeahead()
5fb2e288 54 })
f409f0c3
RK
55 }
56
5fb2e288
C
57 ngAfterViewChecked () {
58 if (this.scheduleKeyboardEventsInit && !this.keyboardEventsManager) {
59 // Avoid ExpressionChangedAfterItHasBeenCheckedError errors
60 setTimeout(() => this.initKeyboardEventsManager(), 0)
61 }
6af662a5
RK
62 }
63
5fb2e288
C
64 ngOnDestroy () {
65 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
6af662a5
RK
66 }
67
5fb2e288 68 areInstructionsDisplayed () {
9b8a7aa8
RK
69 return !this.search
70 }
71
5fb2e288
C
72 showSearchGlobalHelp () {
73 return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index'
6af662a5
RK
74 }
75
5fb2e288 76 canSearchAnyURI () {
9b8a7aa8 77 if (!this.serverConfig) return false
5fb2e288 78
9b8a7aa8
RK
79 return this.authService.isLoggedIn()
80 ? this.serverConfig.search.remoteUri.users
81 : this.serverConfig.search.remoteUri.anonymous
82 }
83
84 onSearchChange () {
5fb2e288
C
85 this.computeTypeahead()
86 }
87
88 initKeyboardEventsManager () {
89 if (this.keyboardEventsManager) return
90
91 this.keyboardEventsManager = new ListKeyManager(this.suggestionItems)
92
93 const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
94 if (activeIndex === -1) {
42b40636 95 logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
f409f0c3
RK
96 }
97
5fb2e288
C
98 this.updateItemsState(activeIndex)
99
100 this.keyboardEventsManager.change.subscribe(
101 _ => this.updateItemsState()
f409f0c3
RK
102 )
103 }
104
5fb2e288
C
105 computeTypeahead () {
106 const searchIndexConfig = this.serverConfig.search.searchIndex
107
108 if (!this.activeSearch) {
3b0bd70a 109 if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) {
5fb2e288 110 this.activeSearch = 'search-index'
3b0bd70a
C
111 } else {
112 this.activeSearch = 'search-instance'
52cc0d54 113 }
5fb2e288
C
114 }
115
116 this.areSuggestionsOpened = true
117 this.results = []
118
119 if (!this.search) return
120
121 if (searchIndexConfig.enabled === false || searchIndexConfig.disableLocalSearch !== true) {
122 this.results.push({
123 text: this.search,
124 type: 'search-instance',
125 default: this.activeSearch === 'search-instance'
126 })
127 }
128
129 if (searchIndexConfig.enabled) {
130 this.results.push({
131 text: this.search,
132 type: 'search-index',
133 default: this.activeSearch === 'search-index'
134 })
135 }
136
137 this.scheduleKeyboardEventsInit = true
52cc0d54
RK
138 }
139
5fb2e288
C
140 updateItemsState (index?: number) {
141 if (index !== undefined) {
142 this.keyboardEventsManager.setActiveItem(index)
143 }
8a979d73 144
5fb2e288
C
145 for (const item of this.suggestionItems) {
146 if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === item) {
147 item.active = true
148 this.activeSearch = item.result.type
149 continue
150 }
8a979d73 151
5fb2e288 152 item.active = false
6af662a5 153 }
5fb2e288 154 }
8a979d73 155
5bd427e0 156 onSuggestionClicked (payload: SuggestionPayload) {
5fb2e288
C
157 this.doSearch(this.buildSearchTarget(payload))
158 }
159
160 onSuggestionHover (index: number) {
161 this.updateItemsState(index)
6af662a5
RK
162 }
163
be6343d2 164 handleKey (event: KeyboardEvent) {
8a979d73 165 if (!this.keyboardEventsManager) return
4c1c1709 166
8a979d73 167 switch (event.key) {
4c1c1709
C
168 case 'ArrowDown':
169 case 'ArrowUp':
5fb2e288
C
170 event.stopPropagation()
171
f409f0c3 172 this.keyboardEventsManager.onKeydown(event)
8a979d73 173 break
5bd427e0
C
174
175 case 'Enter':
176 event.stopPropagation()
177 this.doSearch()
178 break
f409f0c3
RK
179 }
180 }
52cc0d54 181
36004aa7
RK
182 isOnSearch () {
183 return window.location.pathname === '/search'
184 }
185
5fb2e288
C
186 doSearch (searchTarget?: SearchTargetType) {
187 this.areSuggestionsOpened = false
52cc0d54
RK
188 const queryParams: Params = {}
189
36004aa7 190 if (this.isOnSearch() && this.route.snapshot.queryParams) {
52cc0d54
RK
191 Object.assign(queryParams, this.route.snapshot.queryParams)
192 }
193
5fb2e288
C
194 if (!searchTarget) {
195 searchTarget = this.buildSearchTarget(this.keyboardEventsManager.activeItem.result)
196 }
197
198 Object.assign(queryParams, { search: this.search, searchTarget })
52cc0d54
RK
199
200 const o = this.authService.isLoggedIn()
201 ? this.loadUserLanguagesIfNeeded(queryParams)
202 : of(true)
203
204 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
205 }
206
207 private loadUserLanguagesIfNeeded (queryParams: any) {
9df52d66 208 if (queryParams?.languageOneOf) return of(queryParams)
52cc0d54
RK
209
210 return this.authService.userInformationLoaded
211 .pipe(
212 first(),
213 tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages }))
214 )
215 }
5fb2e288
C
216
217 private buildSearchTarget (result: SuggestionPayload): SearchTargetType {
218 if (result.type === 'search-index') {
219 return 'search-index'
220 }
221
222 return 'local'
223 }
f409f0c3 224}