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