36e18d018bffef0735eeaeac1f6f37278ddc05fd
[oweals/peertube.git] / client / src / app / shared / video / video-miniature.component.ts
1 import { switchMap } from 'rxjs/operators'
2 import {
3   ChangeDetectionStrategy,
4   ChangeDetectorRef,
5   Component,
6   EventEmitter,
7   Inject,
8   Input,
9   LOCALE_ID,
10   OnInit,
11   Output
12 } from '@angular/core'
13 import { AuthService, ServerService } from '@app/core'
14 import { ScreenService } from '@app/shared/misc/screen.service'
15 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
16 import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
17 import { I18n } from '@ngx-translate/i18n-polyfill'
18 import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared'
19 import { User } from '../users'
20 import { Video } from './video.model'
21
22 export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
23 export type MiniatureDisplayOptions = {
24   date?: boolean
25   views?: boolean
26   by?: boolean
27   avatar?: boolean
28   privacyLabel?: boolean
29   privacyText?: boolean
30   state?: boolean
31   blacklistInfo?: boolean
32   nsfw?: boolean
33 }
34
35 @Component({
36   selector: 'my-video-miniature',
37   styleUrls: [ './video-miniature.component.scss' ],
38   templateUrl: './video-miniature.component.html',
39   changeDetection: ChangeDetectionStrategy.OnPush
40 })
41 export class VideoMiniatureComponent implements OnInit {
42   @Input() user: User
43   @Input() video: Video
44
45   @Input() ownerDisplayType: OwnerDisplayType = 'account'
46   @Input() displayOptions: MiniatureDisplayOptions = {
47     date: true,
48     views: true,
49     by: true,
50     avatar: false,
51     privacyLabel: false,
52     privacyText: false,
53     state: false,
54     blacklistInfo: false
55   }
56   @Input() displayAsRow = false
57   @Input() displayVideoActions = true
58   @Input() fitWidth = false
59
60   @Input() useLazyLoadUrl = false
61
62   @Output() videoBlocked = new EventEmitter()
63   @Output() videoUnblocked = new EventEmitter()
64   @Output() videoRemoved = new EventEmitter()
65
66   videoActionsDisplayOptions: VideoActionsDisplayType = {
67     playlist: true,
68     download: false,
69     update: true,
70     blacklist: true,
71     delete: true,
72     report: true,
73     duplicate: true
74   }
75   showActions = false
76   serverConfig: ServerConfig
77
78   addToWatchLaterText: string
79   addedToWatchLaterText: string
80   inWatchLaterPlaylist: boolean
81   channelLinkTitle = ''
82
83   watchLaterPlaylist: {
84     id: number
85     playlistElementId?: number
86   }
87
88   videoLink: any[] = []
89
90   private ownerDisplayTypeChosen: 'account' | 'videoChannel'
91
92   constructor (
93     private screenService: ScreenService,
94     private serverService: ServerService,
95     private i18n: I18n,
96     private authService: AuthService,
97     private videoPlaylistService: VideoPlaylistService,
98     private cd: ChangeDetectorRef,
99     @Inject(LOCALE_ID) private localeId: string
100   ) {}
101
102   get isVideoBlur () {
103     return this.video.isVideoNSFWForUser(this.user, this.serverConfig)
104   }
105
106   ngOnInit () {
107     this.serverConfig = this.serverService.getTmpConfig()
108     this.serverService.getConfig()
109         .subscribe(config => {
110           this.serverConfig = config
111           this.buildVideoLink()
112         })
113
114     this.setUpBy()
115
116     this.channelLinkTitle = this.i18n(
117       'Go to the channel page of {{name}} ({{handle}})',
118       { name: this.video.channel.name, handle: this.video.byVideoChannel }
119     )
120
121     // We rely on mouseenter to lazy load actions
122     if (this.screenService.isInTouchScreen()) {
123       this.loadActions()
124     }
125   }
126
127   buildVideoLink () {
128     if (this.useLazyLoadUrl && this.video.url) {
129       const remoteUriConfig = this.serverConfig.search.remoteUri
130
131       // Redirect on the external instance if not allowed to fetch remote data
132       const externalRedirect = (!this.authService.isLoggedIn() && !remoteUriConfig.anonymous) || !remoteUriConfig.users
133       const fromPath = window.location.pathname + window.location.search
134
135       this.videoLink = [ '/search/lazy-load-video', { url: this.video.url, externalRedirect, fromPath } ]
136       return
137     }
138
139     this.videoLink = [ '/videos/watch', this.video.uuid ]
140   }
141
142   displayOwnerAccount () {
143     return this.ownerDisplayTypeChosen === 'account'
144   }
145
146   displayOwnerVideoChannel () {
147     return this.ownerDisplayTypeChosen === 'videoChannel'
148   }
149
150   isUnlistedVideo () {
151     return this.video.privacy.id === VideoPrivacy.UNLISTED
152   }
153
154   isPrivateVideo () {
155     return this.video.privacy.id === VideoPrivacy.PRIVATE
156   }
157
158   getStateLabel (video: Video) {
159     if (!video.state) return ''
160
161     if (video.privacy.id !== VideoPrivacy.PRIVATE && video.state.id === VideoState.PUBLISHED) {
162       return this.i18n('Published')
163     }
164
165     if (video.scheduledUpdate) {
166       const updateAt = new Date(video.scheduledUpdate.updateAt.toString()).toLocaleString(this.localeId)
167       return this.i18n('Publication scheduled on ') + updateAt
168     }
169
170     if (video.state.id === VideoState.TO_TRANSCODE && video.waitTranscoding === true) {
171       return this.i18n('Waiting transcoding')
172     }
173
174     if (video.state.id === VideoState.TO_TRANSCODE) {
175       return this.i18n('To transcode')
176     }
177
178     if (video.state.id === VideoState.TO_IMPORT) {
179       return this.i18n('To import')
180     }
181
182     return ''
183   }
184
185   getAvatarUrl () {
186     if (this.ownerDisplayTypeChosen === 'account') {
187       return this.video.accountAvatarUrl
188     }
189
190     return this.video.videoChannelAvatarUrl
191   }
192
193   loadActions () {
194     if (this.displayVideoActions) this.showActions = true
195
196     this.loadWatchLater()
197   }
198
199   onVideoBlocked () {
200     this.videoBlocked.emit()
201   }
202
203   onVideoUnblocked () {
204     this.videoUnblocked.emit()
205   }
206
207   onVideoRemoved () {
208     this.videoRemoved.emit()
209   }
210
211   isUserLoggedIn () {
212     return this.authService.isLoggedIn()
213   }
214
215   onWatchLaterClick (currentState: boolean) {
216     if (currentState === true) this.removeFromWatchLater()
217     else this.addToWatchLater()
218
219     this.inWatchLaterPlaylist = !currentState
220   }
221
222   addToWatchLater () {
223     const body = { videoId: this.video.id }
224
225     this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body).subscribe(
226       res => {
227         this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
228       }
229     )
230   }
231
232   removeFromWatchLater () {
233     this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId, this.video.id)
234         .subscribe(
235           _ => { /* empty */ }
236         )
237   }
238
239   isWatchLaterPlaylistDisplayed () {
240     return this.displayVideoActions && this.isUserLoggedIn() && this.inWatchLaterPlaylist !== undefined
241   }
242
243   private setUpBy () {
244     if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
245       this.ownerDisplayTypeChosen = this.ownerDisplayType
246       return
247     }
248
249     // If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12)
250     // -> Use the account name
251     if (
252       this.video.channel.name === `${this.video.account.name}_channel` ||
253       this.video.channel.name.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
254     ) {
255       this.ownerDisplayTypeChosen = 'account'
256     } else {
257       this.ownerDisplayTypeChosen = 'videoChannel'
258     }
259   }
260
261   private loadWatchLater () {
262     if (!this.isUserLoggedIn() || this.inWatchLaterPlaylist !== undefined) return
263
264     this.authService.userInformationLoaded
265         .pipe(switchMap(() => this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)))
266         .subscribe(existResult => {
267           const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)
268           const existsInWatchLater = existResult.find(r => r.playlistId === watchLaterPlaylist.id)
269           this.inWatchLaterPlaylist = false
270
271           this.watchLaterPlaylist = {
272             id: watchLaterPlaylist.id
273           }
274
275           if (existsInWatchLater) {
276             this.inWatchLaterPlaylist = true
277             this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId
278           }
279
280           this.cd.markForCheck()
281         })
282
283     this.videoPlaylistService.runPlaylistCheck(this.video.id)
284   }
285 }