]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/+search/search.component.ts
Remove ngx-meta
[github/Chocobozzz/PeerTube.git] / client / src / app / +search / search.component.ts
1 import { forkJoin, of, Subscription } from 'rxjs'
2 import { Component, OnDestroy, OnInit } from '@angular/core'
3 import { ActivatedRoute, Router } from '@angular/router'
4 import { AuthService, ComponentPagination, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core'
5 import { immutableAssign } from '@app/helpers'
6 import { Video, VideoChannel } from '@app/shared/shared-main'
7 import { AdvancedSearch, SearchService } from '@app/shared/shared-search'
8 import { MiniatureDisplayOptions, VideoLinkType } from '@app/shared/shared-video-miniature'
9 import { SearchTargetType, ServerConfig } from '@shared/models'
10
11 @Component({
12 selector: 'my-search',
13 styleUrls: [ './search.component.scss' ],
14 templateUrl: './search.component.html'
15 })
16 export class SearchComponent implements OnInit, OnDestroy {
17 results: (Video | VideoChannel)[] = []
18
19 pagination: ComponentPagination = {
20 currentPage: 1,
21 itemsPerPage: 10, // Only for videos, use another variable for channels
22 totalItems: null
23 }
24 advancedSearch: AdvancedSearch = new AdvancedSearch()
25 isSearchFilterCollapsed = true
26 currentSearch: string
27
28 videoDisplayOptions: MiniatureDisplayOptions = {
29 date: true,
30 views: true,
31 by: true,
32 avatar: false,
33 privacyLabel: false,
34 privacyText: false,
35 state: false,
36 blacklistInfo: false
37 }
38
39 errorMessage: string
40 serverConfig: ServerConfig
41
42 userMiniature: User
43
44 private subActivatedRoute: Subscription
45 private isInitialLoad = false // set to false to show the search filters on first arrival
46 private firstSearch = true
47
48 private channelsPerPage = 2
49
50 private lastSearchTarget: SearchTargetType
51
52 constructor (
53 private route: ActivatedRoute,
54 private router: Router,
55 private metaService: MetaService,
56 private notifier: Notifier,
57 private searchService: SearchService,
58 private authService: AuthService,
59 private userService: UserService,
60 private hooks: HooksService,
61 private serverService: ServerService
62 ) { }
63
64 ngOnInit () {
65 this.serverService.getConfig()
66 .subscribe(config => this.serverConfig = config)
67
68 this.subActivatedRoute = this.route.queryParams.subscribe(
69 async queryParams => {
70 const querySearch = queryParams['search']
71 const searchTarget = queryParams['searchTarget']
72
73 // Search updated, reset filters
74 if (this.currentSearch !== querySearch || searchTarget !== this.advancedSearch.searchTarget) {
75 this.resetPagination()
76 this.advancedSearch.reset()
77
78 this.currentSearch = querySearch || undefined
79 this.updateTitle()
80 }
81
82 this.advancedSearch = new AdvancedSearch(queryParams)
83 if (!this.advancedSearch.searchTarget) {
84 this.advancedSearch.searchTarget = await this.serverService.getDefaultSearchTarget()
85 }
86
87 // Don't hide filters if we have some of them AND the user just came on the webpage
88 this.isSearchFilterCollapsed = this.isInitialLoad === false || !this.advancedSearch.containsValues()
89 this.isInitialLoad = false
90
91 this.search()
92 },
93
94 err => this.notifier.error(err.text)
95 )
96
97 this.userService.getAnonymousOrLoggedUser()
98 .subscribe(user => this.userMiniature = user)
99
100 this.hooks.runAction('action:search.init', 'search')
101 }
102
103 ngOnDestroy () {
104 if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe()
105 }
106
107 isVideoChannel (d: VideoChannel | Video): d is VideoChannel {
108 return d instanceof VideoChannel
109 }
110
111 isVideo (v: VideoChannel | Video): v is Video {
112 return v instanceof Video
113 }
114
115 isUserLoggedIn () {
116 return this.authService.isLoggedIn()
117 }
118
119 getVideoLinkType (): VideoLinkType {
120 if (this.advancedSearch.searchTarget === 'search-index') {
121 const remoteUriConfig = this.serverConfig.search.remoteUri
122
123 // Redirect on the external instance if not allowed to fetch remote data
124 if ((!this.isUserLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users) {
125 return 'external'
126 }
127
128 return 'lazy-load'
129 }
130
131 return 'internal'
132 }
133
134 search () {
135 forkJoin([
136 this.getVideosObs(),
137 this.getVideoChannelObs()
138 ]).subscribe(
139 ([videosResult, videoChannelsResult]) => {
140 this.results = this.results
141 .concat(videoChannelsResult.data)
142 .concat(videosResult.data)
143
144 this.pagination.totalItems = videosResult.total + videoChannelsResult.total
145 this.lastSearchTarget = this.advancedSearch.searchTarget
146
147 // Focus on channels if there are no enough videos
148 if (this.firstSearch === true && videosResult.data.length < this.pagination.itemsPerPage) {
149 this.resetPagination()
150 this.firstSearch = false
151
152 this.channelsPerPage = 10
153 this.search()
154 }
155
156 this.firstSearch = false
157 },
158
159 err => {
160 if (this.advancedSearch.searchTarget !== 'search-index') {
161 this.notifier.error(err.message)
162 return
163 }
164
165 this.notifier.error(
166 $localize`Search index is unavailable. Retrying with instance results instead.`,
167 $localize`Search error`
168 )
169 this.advancedSearch.searchTarget = 'local'
170 this.search()
171 }
172 )
173 }
174
175 onNearOfBottom () {
176 // Last page
177 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
178
179 this.pagination.currentPage += 1
180 this.search()
181 }
182
183 onFiltered () {
184 this.resetPagination()
185
186 this.updateUrlFromAdvancedSearch()
187 }
188
189 numberOfFilters () {
190 return this.advancedSearch.size()
191 }
192
193 // Add VideoChannel for typings, but the template already checks "video" argument is a video
194 removeVideoFromArray (video: Video | VideoChannel) {
195 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
196 }
197
198 isExternalChannelUrl () {
199 return this.getVideoLinkType() === 'external'
200 }
201
202 getExternalChannelUrl (channel: VideoChannel) {
203 // Same algorithm than videos
204 if (this.getVideoLinkType() === 'external') {
205 return channel.url
206 }
207
208 // lazy-load or internal
209 return undefined
210 }
211
212 getInternalChannelUrl (channel: VideoChannel) {
213 const linkType = this.getVideoLinkType()
214
215 if (linkType === 'internal') {
216 return [ '/video-channels', channel.nameWithHost ]
217 }
218
219 if (linkType === 'lazy-load') {
220 return [ '/search/lazy-load-channel', { url: channel.url } ]
221 }
222
223 // external
224 return undefined
225 }
226
227 hideActions () {
228 return this.lastSearchTarget === 'search-index'
229 }
230
231 private resetPagination () {
232 this.pagination.currentPage = 1
233 this.pagination.totalItems = null
234 this.channelsPerPage = 2
235
236 this.results = []
237 }
238
239 private updateTitle () {
240 const suffix = this.currentSearch
241 ? ' ' + this.currentSearch
242 : ''
243
244 this.metaService.setTitle($localize`Search` + suffix)
245 }
246
247 private updateUrlFromAdvancedSearch () {
248 const search = this.currentSearch || undefined
249
250 this.router.navigate([], {
251 relativeTo: this.route,
252 queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search })
253 })
254 }
255
256 private getVideosObs () {
257 const params = {
258 search: this.currentSearch,
259 componentPagination: this.pagination,
260 advancedSearch: this.advancedSearch
261 }
262
263 return this.hooks.wrapObsFun(
264 this.searchService.searchVideos.bind(this.searchService),
265 params,
266 'search',
267 'filter:api.search.videos.list.params',
268 'filter:api.search.videos.list.result'
269 )
270 }
271
272 private getVideoChannelObs () {
273 if (!this.currentSearch) return of({ data: [], total: 0 })
274
275 const params = {
276 search: this.currentSearch,
277 componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }),
278 searchTarget: this.advancedSearch.searchTarget
279 }
280
281 return this.hooks.wrapObsFun(
282 this.searchService.searchVideoChannels.bind(this.searchService),
283 params,
284 'search',
285 'filter:api.search.video-channels.list.params',
286 'filter:api.search.video-channels.list.result'
287 )
288 }
289 }