1 import { of } from 'rxjs'
2 import { first, tap } from 'rxjs/operators'
3 import { ListKeyManager } from '@angular/cdk/a11y'
4 import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
5 import { ActivatedRoute, Params, Router } from '@angular/router'
6 import { AuthService, ServerService } from '@app/core'
7 import { HTMLServerConfig, SearchTargetType } from '@shared/models'
8 import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
11 selector: 'my-search-typeahead',
12 templateUrl: './search-typeahead.component.html',
13 styleUrls: [ './search-typeahead.component.scss' ]
15 export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDestroy {
16 @ViewChildren(SuggestionComponent) suggestionItems: QueryList<SuggestionComponent>
20 areSuggestionsOpened = true
23 serverConfig: HTMLServerConfig
25 inThisChannelText: string
27 keyboardEventsManager: ListKeyManager<SuggestionComponent>
28 results: SuggestionPayload[] = []
30 activeSearch: SuggestionPayloadType
32 private scheduleKeyboardEventsInit = false
35 private authService: AuthService,
36 private router: Router,
37 private route: ActivatedRoute,
38 private serverService: ServerService
42 this.route.queryParams
43 .pipe(first(params => this.isOnSearch() && params.search !== undefined && params.search !== null))
44 .subscribe(params => this.search = params.search)
46 this.serverConfig = this.serverService.getHTMLConfig()
47 this.computeTypeahead()
49 this.serverService.configReloaded
50 .subscribe(config => {
51 this.serverConfig = config
52 this.computeTypeahead()
56 ngAfterViewChecked () {
57 if (this.scheduleKeyboardEventsInit && !this.keyboardEventsManager) {
58 // Avoid ExpressionChangedAfterItHasBeenCheckedError errors
59 setTimeout(() => this.initKeyboardEventsManager(), 0)
64 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
67 areInstructionsDisplayed () {
71 showSearchGlobalHelp () {
72 return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index'
76 if (!this.serverConfig) return false
78 return this.authService.isLoggedIn()
79 ? this.serverConfig.search.remoteUri.users
80 : this.serverConfig.search.remoteUri.anonymous
84 this.computeTypeahead()
87 initKeyboardEventsManager () {
88 if (this.keyboardEventsManager) return
90 this.keyboardEventsManager = new ListKeyManager(this.suggestionItems)
92 const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
93 if (activeIndex === -1) {
94 console.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
97 this.updateItemsState(activeIndex)
99 this.keyboardEventsManager.change.subscribe(
100 _ => this.updateItemsState()
104 computeTypeahead () {
105 const searchIndexConfig = this.serverConfig.search.searchIndex
107 if (!this.activeSearch) {
108 if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) {
109 this.activeSearch = 'search-index'
111 this.activeSearch = 'search-instance'
115 this.areSuggestionsOpened = true
118 if (!this.search) return
120 if (searchIndexConfig.enabled === false || searchIndexConfig.disableLocalSearch !== true) {
123 type: 'search-instance',
124 default: this.activeSearch === 'search-instance'
128 if (searchIndexConfig.enabled) {
131 type: 'search-index',
132 default: this.activeSearch === 'search-index'
136 this.scheduleKeyboardEventsInit = true
139 updateItemsState (index?: number) {
140 if (index !== undefined) {
141 this.keyboardEventsManager.setActiveItem(index)
144 for (const item of this.suggestionItems) {
145 if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === item) {
147 this.activeSearch = item.result.type
155 onSuggestionClicked (payload: SuggestionPayload) {
156 this.doSearch(this.buildSearchTarget(payload))
159 onSuggestionHover (index: number) {
160 this.updateItemsState(index)
163 handleKey (event: KeyboardEvent) {
164 if (!this.keyboardEventsManager) return
169 event.stopPropagation()
171 this.keyboardEventsManager.onKeydown(event)
175 event.stopPropagation()
182 return window.location.pathname === '/search'
185 doSearch (searchTarget?: SearchTargetType) {
186 this.areSuggestionsOpened = false
187 const queryParams: Params = {}
189 if (this.isOnSearch() && this.route.snapshot.queryParams) {
190 Object.assign(queryParams, this.route.snapshot.queryParams)
194 searchTarget = this.buildSearchTarget(this.keyboardEventsManager.activeItem.result)
197 Object.assign(queryParams, { search: this.search, searchTarget })
199 const o = this.authService.isLoggedIn()
200 ? this.loadUserLanguagesIfNeeded(queryParams)
203 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
206 private loadUserLanguagesIfNeeded (queryParams: any) {
207 if (queryParams?.languageOneOf) return of(queryParams)
209 return this.authService.userInformationLoaded
212 tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages }))
216 private buildSearchTarget (result: SuggestionPayload): SearchTargetType {
217 if (result.type === 'search-index') {
218 return 'search-index'