aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+search/search.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+search/search.component.ts')
-rw-r--r--client/src/app/+search/search.component.ts146
1 files changed, 85 insertions, 61 deletions
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts
index a4ab7a5b1..fdf9b7cc0 100644
--- a/client/src/app/+search/search.component.ts
+++ b/client/src/app/+search/search.component.ts
@@ -1,11 +1,13 @@
1import { forkJoin, of, Subscription } from 'rxjs' 1import { forkJoin, of, Subscription } from 'rxjs'
2import { LinkType } from 'src/types/link.type'
2import { Component, OnDestroy, OnInit } from '@angular/core' 3import { Component, OnDestroy, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ComponentPagination, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core' 5import { AuthService, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core'
5import { immutableAssign } from '@app/helpers' 6import { immutableAssign } from '@app/helpers'
6import { Video, VideoChannel } from '@app/shared/shared-main' 7import { Video, VideoChannel } from '@app/shared/shared-main'
7import { AdvancedSearch, SearchService } from '@app/shared/shared-search' 8import { AdvancedSearch, SearchService } from '@app/shared/shared-search'
8import { MiniatureDisplayOptions, VideoLinkType } from '@app/shared/shared-video-miniature' 9import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
10import { VideoPlaylist } from '@app/shared/shared-video-playlist'
9import { HTMLServerConfig, SearchTargetType } from '@shared/models' 11import { HTMLServerConfig, SearchTargetType } from '@shared/models'
10 12
11@Component({ 13@Component({
@@ -16,10 +18,9 @@ import { HTMLServerConfig, SearchTargetType } from '@shared/models'
16export class SearchComponent implements OnInit, OnDestroy { 18export class SearchComponent implements OnInit, OnDestroy {
17 results: (Video | VideoChannel)[] = [] 19 results: (Video | VideoChannel)[] = []
18 20
19 pagination: ComponentPagination = { 21 pagination = {
20 currentPage: 1, 22 currentPage: 1,
21 itemsPerPage: 10, // Only for videos, use another variable for channels 23 totalItems: null as number
22 totalItems: null
23 } 24 }
24 advancedSearch: AdvancedSearch = new AdvancedSearch() 25 advancedSearch: AdvancedSearch = new AdvancedSearch()
25 isSearchFilterCollapsed = true 26 isSearchFilterCollapsed = true
@@ -45,6 +46,11 @@ export class SearchComponent implements OnInit, OnDestroy {
45 private firstSearch = true 46 private firstSearch = true
46 47
47 private channelsPerPage = 2 48 private channelsPerPage = 2
49 private playlistsPerPage = 2
50 private videosPerPage = 10
51
52 private hasMoreResults = true
53 private isSearching = false
48 54
49 private lastSearchTarget: SearchTargetType 55 private lastSearchTarget: SearchTargetType
50 56
@@ -104,77 +110,62 @@ export class SearchComponent implements OnInit, OnDestroy {
104 if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe() 110 if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe()
105 } 111 }
106 112
107 isVideoChannel (d: VideoChannel | Video): d is VideoChannel { 113 isVideoChannel (d: VideoChannel | Video | VideoPlaylist): d is VideoChannel {
108 return d instanceof VideoChannel 114 return d instanceof VideoChannel
109 } 115 }
110 116
111 isVideo (v: VideoChannel | Video): v is Video { 117 isVideo (v: VideoChannel | Video | VideoPlaylist): v is Video {
112 return v instanceof Video 118 return v instanceof Video
113 } 119 }
114 120
121 isPlaylist (v: VideoChannel | Video | VideoPlaylist): v is VideoPlaylist {
122 return v instanceof VideoPlaylist
123 }
124
115 isUserLoggedIn () { 125 isUserLoggedIn () {
116 return this.authService.isLoggedIn() 126 return this.authService.isLoggedIn()
117 } 127 }
118 128
119 getVideoLinkType (): VideoLinkType { 129 search () {
120 if (this.advancedSearch.searchTarget === 'search-index') { 130 this.isSearching = true
121 const remoteUriConfig = this.serverConfig.search.remoteUri
122 131
123 // Redirect on the external instance if not allowed to fetch remote data 132 forkJoin([
124 if ((!this.isUserLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users) { 133 this.getVideoChannelObs(),
125 return 'external' 134 this.getVideoPlaylistObs(),
135 this.getVideosObs()
136 ]).subscribe(results => {
137 for (const result of results) {
138 this.results = this.results.concat(result.data)
126 } 139 }
127 140
128 return 'lazy-load' 141 this.pagination.totalItems = results.reduce((p, r) => p += r.total, 0)
129 } 142 this.lastSearchTarget = this.advancedSearch.searchTarget
130 143
131 return 'internal' 144 this.hasMoreResults = this.results.length < this.pagination.totalItems
132 } 145 },
133 146
134 search () { 147 err => {
135 forkJoin([ 148 if (this.advancedSearch.searchTarget !== 'search-index') {
136 this.getVideosObs(), 149 this.notifier.error(err.message)
137 this.getVideoChannelObs() 150 return
138 ]).subscribe( 151 }
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 152
159 err => { 153 this.notifier.error(
160 if (this.advancedSearch.searchTarget !== 'search-index') { 154 $localize`Search index is unavailable. Retrying with instance results instead.`,
161 this.notifier.error(err.message) 155 $localize`Search error`
162 return 156 )
163 } 157 this.advancedSearch.searchTarget = 'local'
158 this.search()
159 },
164 160
165 this.notifier.error( 161 () => {
166 $localize`Search index is unavailable. Retrying with instance results instead.`, 162 this.isSearching = false
167 $localize`Search error` 163 })
168 )
169 this.advancedSearch.searchTarget = 'local'
170 this.search()
171 }
172 )
173 } 164 }
174 165
175 onNearOfBottom () { 166 onNearOfBottom () {
176 // Last page 167 // Last page
177 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return 168 if (!this.hasMoreResults || this.isSearching) return
178 169
179 this.pagination.currentPage += 1 170 this.pagination.currentPage += 1
180 this.search() 171 this.search()
@@ -190,18 +181,33 @@ export class SearchComponent implements OnInit, OnDestroy {
190 return this.advancedSearch.size() 181 return this.advancedSearch.size()
191 } 182 }
192 183
193 // Add VideoChannel for typings, but the template already checks "video" argument is a video 184 // Add VideoChannel/VideoPlaylist for typings, but the template already checks "video" argument is a video
194 removeVideoFromArray (video: Video | VideoChannel) { 185 removeVideoFromArray (video: Video | VideoChannel | VideoPlaylist) {
195 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id) 186 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
196 } 187 }
197 188
189 getLinkType (): LinkType {
190 if (this.advancedSearch.searchTarget === 'search-index') {
191 const remoteUriConfig = this.serverConfig.search.remoteUri
192
193 // Redirect on the external instance if not allowed to fetch remote data
194 if ((!this.isUserLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users) {
195 return 'external'
196 }
197
198 return 'lazy-load'
199 }
200
201 return 'internal'
202 }
203
198 isExternalChannelUrl () { 204 isExternalChannelUrl () {
199 return this.getVideoLinkType() === 'external' 205 return this.getLinkType() === 'external'
200 } 206 }
201 207
202 getExternalChannelUrl (channel: VideoChannel) { 208 getExternalChannelUrl (channel: VideoChannel) {
203 // Same algorithm than videos 209 // Same algorithm than videos
204 if (this.getVideoLinkType() === 'external') { 210 if (this.getLinkType() === 'external') {
205 return channel.url 211 return channel.url
206 } 212 }
207 213
@@ -210,7 +216,7 @@ export class SearchComponent implements OnInit, OnDestroy {
210 } 216 }
211 217
212 getInternalChannelUrl (channel: VideoChannel) { 218 getInternalChannelUrl (channel: VideoChannel) {
213 const linkType = this.getVideoLinkType() 219 const linkType = this.getLinkType()
214 220
215 if (linkType === 'internal') { 221 if (linkType === 'internal') {
216 return [ '/c', channel.nameWithHost ] 222 return [ '/c', channel.nameWithHost ]
@@ -256,7 +262,7 @@ export class SearchComponent implements OnInit, OnDestroy {
256 private getVideosObs () { 262 private getVideosObs () {
257 const params = { 263 const params = {
258 search: this.currentSearch, 264 search: this.currentSearch,
259 componentPagination: this.pagination, 265 componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.videosPerPage }),
260 advancedSearch: this.advancedSearch 266 advancedSearch: this.advancedSearch
261 } 267 }
262 268
@@ -287,6 +293,24 @@ export class SearchComponent implements OnInit, OnDestroy {
287 ) 293 )
288 } 294 }
289 295
296 private getVideoPlaylistObs () {
297 if (!this.currentSearch) return of({ data: [], total: 0 })
298
299 const params = {
300 search: this.currentSearch,
301 componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.playlistsPerPage }),
302 searchTarget: this.advancedSearch.searchTarget
303 }
304
305 return this.hooks.wrapObsFun(
306 this.searchService.searchVideoPlaylists.bind(this.searchService),
307 params,
308 'search',
309 'filter:api.search.video-playlists.list.params',
310 'filter:api.search.video-playlists.list.result'
311 )
312 }
313
290 private getDefaultSearchTarget (): SearchTargetType { 314 private getDefaultSearchTarget (): SearchTargetType {
291 const searchIndexConfig = this.serverConfig.search.searchIndex 315 const searchIndexConfig = this.serverConfig.search.searchIndex
292 316