]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/+search/search.component.ts
Remove ngx-meta
[github/Chocobozzz/PeerTube.git] / client / src / app / +search / search.component.ts
CommitLineData
5fb2e288 1import { forkJoin, of, Subscription } from 'rxjs'
57c36b27 2import { Component, OnDestroy, OnInit } from '@angular/core'
0b18f4aa 3import { ActivatedRoute, Router } from '@angular/router'
0f01a8ba 4import { AuthService, ComponentPagination, HooksService, MetaService, Notifier, ServerService, User, UserService } from '@app/core'
67ed6552
C
5import { immutableAssign } from '@app/helpers'
6import { Video, VideoChannel } from '@app/shared/shared-main'
1942f11d 7import { AdvancedSearch, SearchService } from '@app/shared/shared-search'
0bdad52f 8import { MiniatureDisplayOptions, VideoLinkType } from '@app/shared/shared-video-miniature'
67ed6552 9import { SearchTargetType, ServerConfig } from '@shared/models'
57c36b27
C
10
11@Component({
12 selector: 'my-search',
13 styleUrls: [ './search.component.scss' ],
14 templateUrl: './search.component.html'
15})
16export class SearchComponent implements OnInit, OnDestroy {
26fabbd6 17 results: (Video | VideoChannel)[] = []
f37dc0dd 18
57c36b27
C
19 pagination: ComponentPagination = {
20 currentPage: 1,
f37dc0dd 21 itemsPerPage: 10, // Only for videos, use another variable for channels
57c36b27
C
22 totalItems: null
23 }
0b18f4aa
C
24 advancedSearch: AdvancedSearch = new AdvancedSearch()
25 isSearchFilterCollapsed = true
f37dc0dd 26 currentSearch: string
57c36b27 27
cf78883c
C
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
5fb2e288
C
39 errorMessage: string
40 serverConfig: ServerConfig
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
b1ee8526 46 private firstSearch = true
57c36b27 47
f37dc0dd
C
48 private channelsPerPage = 2
49
7c87746e
C
50 private lastSearchTarget: SearchTargetType
51
57c36b27 52 constructor (
57c36b27 53 private route: ActivatedRoute,
0b18f4aa 54 private router: Router,
57c36b27 55 private metaService: MetaService,
f8b2c1b4 56 private notifier: Notifier,
26fabbd6 57 private searchService: SearchService,
93cae479 58 private authService: AuthService,
5c20a455 59 private userService: UserService,
5fb2e288
C
60 private hooks: HooksService,
61 private serverService: ServerService
57c36b27
C
62 ) { }
63
64 ngOnInit () {
5fb2e288
C
65 this.serverService.getConfig()
66 .subscribe(config => this.serverConfig = config)
67
57c36b27 68 this.subActivatedRoute = this.route.queryParams.subscribe(
5fb2e288 69 async queryParams => {
57c36b27 70 const querySearch = queryParams['search']
7c87746e 71 const searchTarget = queryParams['searchTarget']
57c36b27 72
0b18f4aa 73 // Search updated, reset filters
7c87746e 74 if (this.currentSearch !== querySearch || searchTarget !== this.advancedSearch.searchTarget) {
7afea880
C
75 this.resetPagination()
76 this.advancedSearch.reset()
77
47879669 78 this.currentSearch = querySearch || undefined
7afea880
C
79 this.updateTitle()
80 }
81
82 this.advancedSearch = new AdvancedSearch(queryParams)
5fb2e288
C
83 if (!this.advancedSearch.searchTarget) {
84 this.advancedSearch.searchTarget = await this.serverService.getDefaultSearchTarget()
85 }
0b18f4aa 86
7afea880
C
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
57c36b27 90
7afea880 91 this.search()
57c36b27
C
92 },
93
f8b2c1b4 94 err => this.notifier.error(err.text)
57c36b27 95 )
7663e55a 96
5c20a455
C
97 this.userService.getAnonymousOrLoggedUser()
98 .subscribe(user => this.userMiniature = user)
99
c9e3eeed 100 this.hooks.runAction('action:search.init', 'search')
57c36b27
C
101 }
102
103 ngOnDestroy () {
104 if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe()
105 }
106
26fabbd6
C
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
0bdad52f
C
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
57c36b27 134 search () {
f37dc0dd 135 forkJoin([
93cae479
C
136 this.getVideosObs(),
137 this.getVideoChannelObs()
5fb2e288
C
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
7c87746e 145 this.lastSearchTarget = this.advancedSearch.searchTarget
b1ee8526 146
5fb2e288
C
147 // Focus on channels if there are no enough videos
148 if (this.firstSearch === true && videosResult.data.length < this.pagination.itemsPerPage) {
149 this.resetPagination()
b1ee8526 150 this.firstSearch = false
57c36b27 151
5fb2e288
C
152 this.channelsPerPage = 10
153 this.search()
154 }
155
156 this.firstSearch = false
157 },
158
159 err => {
12e6b314
C
160 if (this.advancedSearch.searchTarget !== 'search-index') {
161 this.notifier.error(err.message)
162 return
163 }
5fb2e288
C
164
165 this.notifier.error(
66357162
C
166 $localize`Search index is unavailable. Retrying with instance results instead.`,
167 $localize`Search error`
5fb2e288
C
168 )
169 this.advancedSearch.searchTarget = 'local'
170 this.search()
171 }
172 )
57c36b27
C
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
0b18f4aa 183 onFiltered () {
7afea880 184 this.resetPagination()
0b18f4aa 185
7afea880 186 this.updateUrlFromAdvancedSearch()
0b18f4aa
C
187 }
188
c5d04b4f
RK
189 numberOfFilters () {
190 return this.advancedSearch.size()
191 }
192
be27ef3b
C
193 // Add VideoChannel for typings, but the template already checks "video" argument is a video
194 removeVideoFromArray (video: Video | VideoChannel) {
3a0fb65c
C
195 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
196 }
197
746018f6
C
198 isExternalChannelUrl () {
199 return this.getVideoLinkType() === 'external'
200 }
201
202 getExternalChannelUrl (channel: VideoChannel) {
0bdad52f
C
203 // Same algorithm than videos
204 if (this.getVideoLinkType() === 'external') {
205 return channel.url
206 }
5fb2e288 207
746018f6
C
208 // lazy-load or internal
209 return undefined
210 }
211
212 getInternalChannelUrl (channel: VideoChannel) {
213 const linkType = this.getVideoLinkType()
214
215 if (linkType === 'internal') {
0bdad52f 216 return [ '/video-channels', channel.nameWithHost ]
5fb2e288
C
217 }
218
746018f6
C
219 if (linkType === 'lazy-load') {
220 return [ '/search/lazy-load-channel', { url: channel.url } ]
221 }
222
223 // external
224 return undefined
5fb2e288
C
225 }
226
227 hideActions () {
7c87746e 228 return this.lastSearchTarget === 'search-index'
5fb2e288
C
229 }
230
7afea880 231 private resetPagination () {
57c36b27
C
232 this.pagination.currentPage = 1
233 this.pagination.totalItems = null
aa55a4da 234 this.channelsPerPage = 2
57c36b27 235
26fabbd6 236 this.results = []
57c36b27
C
237 }
238
239 private updateTitle () {
0f01a8ba
C
240 const suffix = this.currentSearch
241 ? ' ' + this.currentSearch
242 : ''
243
66357162 244 this.metaService.setTitle($localize`Search` + suffix)
57c36b27 245 }
0b18f4aa
C
246
247 private updateUrlFromAdvancedSearch () {
47879669 248 const search = this.currentSearch || undefined
c5d04b4f 249
0b18f4aa
C
250 this.router.navigate([], {
251 relativeTo: this.route,
c5d04b4f 252 queryParams: Object.assign({}, this.advancedSearch.toUrlObject(), { search })
0b18f4aa
C
253 })
254 }
93cae479
C
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,
e8f902c0 266 'search',
93cae479
C
267 'filter:api.search.videos.list.params',
268 'filter:api.search.videos.list.result'
269 )
270 }
271
272 private getVideoChannelObs () {
44df5c75
C
273 if (!this.currentSearch) return of({ data: [], total: 0 })
274
93cae479
C
275 const params = {
276 search: this.currentSearch,
5fb2e288
C
277 componentPagination: immutableAssign(this.pagination, { itemsPerPage: this.channelsPerPage }),
278 searchTarget: this.advancedSearch.searchTarget
93cae479
C
279 }
280
281 return this.hooks.wrapObsFun(
282 this.searchService.searchVideoChannels.bind(this.searchService),
283 params,
e8f902c0 284 'search',
93cae479
C
285 'filter:api.search.video-channels.list.params',
286 'filter:api.search.video-channels.list.result'
287 )
288 }
57c36b27 289}