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