]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/header/search-typeahead.component.ts
Add keyboard navigation and hepler to typeahead
[github/Chocobozzz/PeerTube.git] / client / src / app / header / search-typeahead.component.ts
1 import {
2 Component,
3 ViewChild,
4 ElementRef,
5 AfterViewInit,
6 OnInit,
7 OnDestroy,
8 QueryList
9 } from '@angular/core'
10 import { Router, NavigationEnd } from '@angular/router'
11 import { AuthService } from '@app/core'
12 import { I18n } from '@ngx-translate/i18n-polyfill'
13 import { filter } from 'rxjs/operators'
14 import { ListKeyManager } from '@angular/cdk/a11y'
15 import { UP_ARROW, DOWN_ARROW, ENTER, TAB } from '@angular/cdk/keycodes'
16 import { SuggestionComponent } from './suggestion.component'
17
18 @Component({
19 selector: 'my-search-typeahead',
20 templateUrl: './search-typeahead.component.html',
21 styleUrls: [ './search-typeahead.component.scss' ]
22 })
23 export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewInit {
24 @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef
25
26 hasChannel = false
27 inChannel = false
28 newSearch = true
29
30 searchInput: HTMLInputElement
31 URIPolicy: 'only-followed' | 'any' = 'any'
32
33 URIPolicyText: string
34 inAllText: string
35 inThisChannelText: string
36 globalSearchIndex = 'https://index.joinpeertube.org'
37
38 keyboardEventsManager: ListKeyManager<SuggestionComponent>
39 results: any[] = []
40
41 constructor (
42 private authService: AuthService,
43 private router: Router,
44 private i18n: I18n
45 ) {
46 this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.')
47 this.inAllText = this.i18n('In all PeerTube')
48 this.inThisChannelText = this.i18n('In this channel')
49 }
50
51 ngOnInit () {
52 this.router.events
53 .pipe(filter(event => event instanceof NavigationEnd))
54 .subscribe((event: NavigationEnd) => {
55 this.hasChannel = event.url.startsWith('/videos/watch')
56 this.inChannel = event.url.startsWith('/video-channels')
57 this.computeResults()
58 })
59 }
60
61 ngOnDestroy () {
62 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
63 }
64
65 ngAfterViewInit () {
66 this.searchInput = this.contentWrapper.nativeElement.childNodes[0]
67 this.searchInput.addEventListener('input', this.computeResults.bind(this))
68 this.searchInput.addEventListener('keyup', this.handleKeyUp.bind(this))
69 }
70
71 get hasSearch () {
72 return !!this.searchInput && !!this.searchInput.value
73 }
74
75 get activeResult () {
76 return this.keyboardEventsManager && this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem.result
77 }
78
79 get showHelp () {
80 return this.hasSearch && this.newSearch && this.activeResult && this.activeResult.type === 'search-global' || false
81 }
82
83 computeResults () {
84 this.newSearch = true
85 let results = [
86 {
87 text: 'MaƮtre poney',
88 type: 'channel'
89 }
90 ]
91
92 if (this.hasSearch) {
93 results = [
94 {
95 text: this.searchInput.value,
96 type: 'search-channel'
97 },
98 {
99 text: this.searchInput.value,
100 type: 'search-instance'
101 },
102 {
103 text: this.searchInput.value,
104 type: 'search-global'
105 },
106 ...results
107 ]
108 }
109
110 this.results = results.filter(
111 result => {
112 // if we're not in a channel or one of its videos/playlits, show all channel-related results
113 if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel')
114 // if we're in a channel, show all channel-related results except for the channel redirection itself
115 if (this.inChannel) return !(result.type === 'channel')
116 return true
117 }
118 )
119 }
120
121 initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) {
122 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
123 this.keyboardEventsManager = new ListKeyManager(event.items)
124 if (event.index !== undefined) {
125 this.keyboardEventsManager.setActiveItem(event.index)
126 event.items.forEach(e => e.active = false)
127 this.keyboardEventsManager.activeItem.active = true
128 }
129 this.keyboardEventsManager.change.subscribe(
130 val => {
131 event.items.forEach(e => e.active = false)
132 this.keyboardEventsManager.activeItem.active = true
133 }
134 )
135 }
136
137 isUserLoggedIn () {
138 return this.authService.isLoggedIn()
139 }
140
141 handleKeyUp (event: KeyboardEvent, indexSelected?: number) {
142 event.stopImmediatePropagation()
143 if (this.keyboardEventsManager) {
144 if (event.keyCode === TAB) {
145 this.keyboardEventsManager.setNextItemActive()
146 return false
147 } else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
148 this.keyboardEventsManager.onKeydown(event)
149 return false
150 } else if (event.keyCode === ENTER) {
151 this.newSearch = false
152 // this.router.navigate(this.keyboardEventsManager.activeItem.result)
153 return false
154 }
155 }
156 }
157 }