]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/header/search-typeahead.component.ts
Media queries to use variables when possible
[github/Chocobozzz/PeerTube.git] / client / src / app / header / search-typeahead.component.ts
CommitLineData
6af662a5
RK
1import {
2 Component,
3 ViewChild,
4 ElementRef,
5 AfterViewInit,
6 OnInit,
7 OnDestroy,
8 QueryList
9} from '@angular/core'
52cc0d54 10import { Router, NavigationEnd, Params, ActivatedRoute } from '@angular/router'
9677fca7 11import { AuthService, ServerService } from '@app/core'
f409f0c3 12import { I18n } from '@ngx-translate/i18n-polyfill'
52cc0d54 13import { filter, first, tap, map } from 'rxjs/operators'
6af662a5 14import { ListKeyManager } from '@angular/cdk/a11y'
9677fca7 15import { UP_ARROW, DOWN_ARROW, ENTER } from '@angular/cdk/keycodes'
52cc0d54
RK
16import { SuggestionComponent, Result } from './suggestion.component'
17import { of } from 'rxjs'
18import { getParameterByName } from '@app/shared/misc/utils'
9677fca7 19import { ServerConfig } from '@shared/models'
f409f0c3
RK
20
21@Component({
22 selector: 'my-search-typeahead',
23 templateUrl: './search-typeahead.component.html',
24 styleUrls: [ './search-typeahead.component.scss' ]
25})
6af662a5 26export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewInit {
f409f0c3 27 @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef
f409f0c3
RK
28
29 hasChannel = false
30 inChannel = false
6af662a5 31 newSearch = true
f409f0c3
RK
32
33 searchInput: HTMLInputElement
9677fca7 34 serverConfig: ServerConfig
f409f0c3
RK
35
36 URIPolicyText: string
37 inAllText: string
38 inThisChannelText: string
6af662a5 39 globalSearchIndex = 'https://index.joinpeertube.org'
f409f0c3 40
6af662a5 41 keyboardEventsManager: ListKeyManager<SuggestionComponent>
f409f0c3
RK
42 results: any[] = []
43
44 constructor (
45 private authService: AuthService,
46 private router: Router,
52cc0d54 47 private route: ActivatedRoute,
9677fca7 48 private serverService: ServerService,
f409f0c3
RK
49 private i18n: I18n
50 ) {
6af662a5 51 this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.')
f409f0c3
RK
52 this.inAllText = this.i18n('In all PeerTube')
53 this.inThisChannelText = this.i18n('In this channel')
54 }
55
56 ngOnInit () {
57 this.router.events
52cc0d54 58 .pipe(filter(e => e instanceof NavigationEnd))
f409f0c3
RK
59 .subscribe((event: NavigationEnd) => {
60 this.hasChannel = event.url.startsWith('/videos/watch')
61 this.inChannel = event.url.startsWith('/video-channels')
62 this.computeResults()
63 })
52cc0d54
RK
64
65 this.router.events
66 .pipe(
67 filter(e => e instanceof NavigationEnd),
68 map(() => getParameterByName('search', window.location.href))
69 )
70 .subscribe(searchQuery => this.searchInput.value = searchQuery || '')
9677fca7
RK
71
72 this.serverService.getConfig()
73 .subscribe(config => this.serverConfig = config)
f409f0c3
RK
74 }
75
6af662a5
RK
76 ngOnDestroy () {
77 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
78 }
79
f409f0c3
RK
80 ngAfterViewInit () {
81 this.searchInput = this.contentWrapper.nativeElement.childNodes[0]
82 this.searchInput.addEventListener('input', this.computeResults.bind(this))
6af662a5 83 this.searchInput.addEventListener('keyup', this.handleKeyUp.bind(this))
f409f0c3
RK
84 }
85
86 get hasSearch () {
87 return !!this.searchInput && !!this.searchInput.value
88 }
89
6af662a5
RK
90 get activeResult () {
91 return this.keyboardEventsManager && this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem.result
92 }
93
94 get showHelp () {
95 return this.hasSearch && this.newSearch && this.activeResult && this.activeResult.type === 'search-global' || false
96 }
97
9677fca7
RK
98 get URIPolicy (): 'only-followed' | 'any' {
99 return (
100 this.authService.isLoggedIn()
101 ? this.serverConfig.search.remoteUri.users
102 : this.serverConfig.search.remoteUri.anonymous
103 )
104 ? 'any'
105 : 'only-followed'
106 }
107
f409f0c3 108 computeResults () {
6af662a5 109 this.newSearch = true
52cc0d54 110 let results: Result[] = []
f409f0c3
RK
111
112 if (this.hasSearch) {
113 results = [
52cc0d54 114 /* Channel search is still unimplemented. Uncomment when it is.
f409f0c3
RK
115 {
116 text: this.searchInput.value,
117 type: 'search-channel'
118 },
52cc0d54 119 */
6af662a5
RK
120 {
121 text: this.searchInput.value,
52cc0d54
RK
122 type: 'search-instance',
123 default: true
6af662a5 124 },
52cc0d54 125 /* Global search is still unimplemented. Uncomment when it is.
f409f0c3
RK
126 {
127 text: this.searchInput.value,
128 type: 'search-global'
129 },
52cc0d54 130 */
f409f0c3
RK
131 ...results
132 ]
133 }
134
135 this.results = results.filter(
52cc0d54 136 (result: Result) => {
f409f0c3
RK
137 // if we're not in a channel or one of its videos/playlits, show all channel-related results
138 if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel')
139 // if we're in a channel, show all channel-related results except for the channel redirection itself
140 if (this.inChannel) return !(result.type === 'channel')
141 return true
142 }
143 )
144 }
145
52cc0d54
RK
146 setEventItems (event: { items: QueryList<SuggestionComponent>, index?: number }) {
147 event.items.forEach(e => {
148 if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === e) {
149 this.keyboardEventsManager.activeItem.active = true
150 } else {
151 e.active = false
152 }
153 })
154 }
155
6af662a5
RK
156 initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) {
157 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
158 this.keyboardEventsManager = new ListKeyManager(event.items)
159 if (event.index !== undefined) {
160 this.keyboardEventsManager.setActiveItem(event.index)
52cc0d54
RK
161 } else {
162 this.keyboardEventsManager.setFirstItemActive()
6af662a5
RK
163 }
164 this.keyboardEventsManager.change.subscribe(
52cc0d54 165 _ => this.setEventItems(event)
6af662a5
RK
166 )
167 }
168
6af662a5 169 handleKeyUp (event: KeyboardEvent, indexSelected?: number) {
f409f0c3
RK
170 event.stopImmediatePropagation()
171 if (this.keyboardEventsManager) {
52cc0d54 172 if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
f409f0c3
RK
173 this.keyboardEventsManager.onKeydown(event)
174 return false
175 } else if (event.keyCode === ENTER) {
6af662a5 176 this.newSearch = false
52cc0d54 177 this.doSearch()
f409f0c3
RK
178 return false
179 }
180 }
181 }
52cc0d54
RK
182
183 doSearch () {
184 const queryParams: Params = {}
185
186 if (window.location.pathname === '/search' && this.route.snapshot.queryParams) {
187 Object.assign(queryParams, this.route.snapshot.queryParams)
188 }
189
190 Object.assign(queryParams, { search: this.searchInput.value })
191
192 const o = this.authService.isLoggedIn()
193 ? this.loadUserLanguagesIfNeeded(queryParams)
194 : of(true)
195
196 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
197 }
198
199 private loadUserLanguagesIfNeeded (queryParams: any) {
200 if (queryParams && queryParams.languageOneOf) return of(queryParams)
201
202 return this.authService.userInformationLoaded
203 .pipe(
204 first(),
205 tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages }))
206 )
207 }
f409f0c3 208}