diff options
Diffstat (limited to 'client/src/app/shared')
6 files changed, 120 insertions, 95 deletions
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts index fc3b77b2a..d78fdc09f 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { bufferTime, catchError, filter, first, map, share, switchMap } from 'rxjs/operators' | 1 | import { bufferTime, catchError, distinctUntilChanged, filter, first, map, share, switchMap } from 'rxjs/operators' |
2 | import { Injectable } from '@angular/core' | 2 | import { Injectable } from '@angular/core' |
3 | import { Observable, ReplaySubject, Subject } from 'rxjs' | 3 | import { Observable, ReplaySubject, Subject } from 'rxjs' |
4 | import { RestExtractor } from '../rest/rest-extractor.service' | 4 | import { RestExtractor } from '../rest/rest-extractor.service' |
@@ -30,7 +30,6 @@ export class VideoPlaylistService { | |||
30 | // Use a replay subject because we "next" a value before subscribing | 30 | // Use a replay subject because we "next" a value before subscribing |
31 | private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) | 31 | private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) |
32 | private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> | 32 | private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> |
33 | private cachedWatchLaterPlaylists: VideoPlaylist[] | ||
34 | 33 | ||
35 | constructor ( | 34 | constructor ( |
36 | private authHttp: HttpClient, | 35 | private authHttp: HttpClient, |
@@ -39,6 +38,7 @@ export class VideoPlaylistService { | |||
39 | private restService: RestService | 38 | private restService: RestService |
40 | ) { | 39 | ) { |
41 | this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe( | 40 | this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe( |
41 | distinctUntilChanged(), | ||
42 | bufferTime(500), | 42 | bufferTime(500), |
43 | filter(videoIds => videoIds.length !== 0), | 43 | filter(videoIds => videoIds.length !== 0), |
44 | switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), | 44 | switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), |
@@ -224,7 +224,7 @@ export class VideoPlaylistService { | |||
224 | let params = new HttpParams() | 224 | let params = new HttpParams() |
225 | params = this.restService.addObjectParams(params, { videoIds }) | 225 | params = this.restService.addObjectParams(params, { videoIds }) |
226 | 226 | ||
227 | return this.authHttp.get<VideoExistInPlaylist>(url, { params }) | 227 | return this.authHttp.get<VideoExistInPlaylist>(url, { params, headers: { ignoreLoadingBar: '' } }) |
228 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 228 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
229 | } | 229 | } |
230 | } | 230 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index c6fd570b7..036825e61 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html | |||
@@ -1,5 +1,8 @@ | |||
1 | <div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()"> | 1 | <div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()"> |
2 | <my-video-thumbnail #thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail> | 2 | <my-video-thumbnail |
3 | [video]="video" [nsfw]="isVideoBlur" | ||
4 | [displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)" | ||
5 | ></my-video-thumbnail> | ||
3 | 6 | ||
4 | <div class="video-bottom"> | 7 | <div class="video-bottom"> |
5 | <div class="video-miniature-information"> | 8 | <div class="video-miniature-information"> |
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index ba65d33b6..a603f87e5 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -1,12 +1,24 @@ | |||
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, LOCALE_ID, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { |
2 | ChangeDetectionStrategy, | ||
3 | ChangeDetectorRef, | ||
4 | Component, | ||
5 | EventEmitter, | ||
6 | Inject, | ||
7 | Input, | ||
8 | LOCALE_ID, | ||
9 | OnInit, | ||
10 | Output | ||
11 | } from '@angular/core' | ||
2 | import { User } from '../users' | 12 | import { User } from '../users' |
3 | import { Video } from './video.model' | 13 | import { Video } from './video.model' |
4 | import { ServerService } from '@app/core' | 14 | import { AuthService, ServerService } from '@app/core' |
5 | import { ServerConfig, VideoPrivacy, VideoState } from '../../../../../shared' | 15 | import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 16 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' | 17 | import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' |
8 | import { ScreenService } from '@app/shared/misc/screen.service' | 18 | import { ScreenService } from '@app/shared/misc/screen.service' |
9 | import { VideoThumbnailComponent } from './video-thumbnail.component' | 19 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' |
20 | import { forkJoin } from 'rxjs' | ||
21 | import { first } from 'rxjs/operators' | ||
10 | 22 | ||
11 | export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' | 23 | export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' |
12 | export type MiniatureDisplayOptions = { | 24 | export type MiniatureDisplayOptions = { |
@@ -47,8 +59,6 @@ export class VideoMiniatureComponent implements OnInit { | |||
47 | @Output() videoUnblacklisted = new EventEmitter() | 59 | @Output() videoUnblacklisted = new EventEmitter() |
48 | @Output() videoRemoved = new EventEmitter() | 60 | @Output() videoRemoved = new EventEmitter() |
49 | 61 | ||
50 | @ViewChild('thumbnail', { static: true }) thumbnail: VideoThumbnailComponent | ||
51 | |||
52 | videoActionsDisplayOptions: VideoActionsDisplayType = { | 62 | videoActionsDisplayOptions: VideoActionsDisplayType = { |
53 | playlist: true, | 63 | playlist: true, |
54 | download: false, | 64 | download: false, |
@@ -60,14 +70,28 @@ export class VideoMiniatureComponent implements OnInit { | |||
60 | showActions = false | 70 | showActions = false |
61 | serverConfig: ServerConfig | 71 | serverConfig: ServerConfig |
62 | 72 | ||
73 | addToWatchLaterText: string | ||
74 | addedToWatchLaterText: string | ||
75 | inWatchLaterPlaylist: boolean | ||
76 | |||
77 | watchLaterPlaylist: { | ||
78 | id: number | ||
79 | playlistElementId?: number | ||
80 | } | ||
81 | |||
63 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' | 82 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' |
64 | 83 | ||
65 | constructor ( | 84 | constructor ( |
66 | private screenService: ScreenService, | 85 | private screenService: ScreenService, |
67 | private serverService: ServerService, | 86 | private serverService: ServerService, |
68 | private i18n: I18n, | 87 | private i18n: I18n, |
88 | private authService: AuthService, | ||
89 | private videoPlaylistService: VideoPlaylistService, | ||
90 | private cd: ChangeDetectorRef, | ||
69 | @Inject(LOCALE_ID) private localeId: string | 91 | @Inject(LOCALE_ID) private localeId: string |
70 | ) { } | 92 | ) { |
93 | |||
94 | } | ||
71 | 95 | ||
72 | get isVideoBlur () { | 96 | get isVideoBlur () { |
73 | return this.video.isVideoNSFWForUser(this.user, this.serverConfig) | 97 | return this.video.isVideoNSFWForUser(this.user, this.serverConfig) |
@@ -131,7 +155,8 @@ export class VideoMiniatureComponent implements OnInit { | |||
131 | 155 | ||
132 | loadActions () { | 156 | loadActions () { |
133 | if (this.displayVideoActions) this.showActions = true | 157 | if (this.displayVideoActions) this.showActions = true |
134 | this.thumbnail.load() | 158 | |
159 | this.loadWatchLater() | ||
135 | } | 160 | } |
136 | 161 | ||
137 | onVideoBlacklisted () { | 162 | onVideoBlacklisted () { |
@@ -146,6 +171,38 @@ export class VideoMiniatureComponent implements OnInit { | |||
146 | this.videoRemoved.emit() | 171 | this.videoRemoved.emit() |
147 | } | 172 | } |
148 | 173 | ||
174 | isUserLoggedIn () { | ||
175 | return this.authService.isLoggedIn() | ||
176 | } | ||
177 | |||
178 | onWatchLaterClick (currentState: boolean) { | ||
179 | if (currentState === true) this.removeFromWatchLater() | ||
180 | else this.addToWatchLater() | ||
181 | |||
182 | this.inWatchLaterPlaylist = !currentState | ||
183 | } | ||
184 | |||
185 | addToWatchLater () { | ||
186 | const body = { videoId: this.video.id } | ||
187 | |||
188 | this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body).subscribe( | ||
189 | res => { | ||
190 | this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id | ||
191 | } | ||
192 | ) | ||
193 | } | ||
194 | |||
195 | removeFromWatchLater () { | ||
196 | this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId) | ||
197 | .subscribe( | ||
198 | _ => { /* empty */ } | ||
199 | ) | ||
200 | } | ||
201 | |||
202 | isWatchLaterPlaylistDisplayed () { | ||
203 | return this.inWatchLaterPlaylist !== undefined | ||
204 | } | ||
205 | |||
149 | private setUpBy () { | 206 | private setUpBy () { |
150 | if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { | 207 | if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') { |
151 | this.ownerDisplayTypeChosen = this.ownerDisplayType | 208 | this.ownerDisplayTypeChosen = this.ownerDisplayType |
@@ -163,4 +220,29 @@ export class VideoMiniatureComponent implements OnInit { | |||
163 | this.ownerDisplayTypeChosen = 'videoChannel' | 220 | this.ownerDisplayTypeChosen = 'videoChannel' |
164 | } | 221 | } |
165 | } | 222 | } |
223 | |||
224 | private loadWatchLater () { | ||
225 | if (!this.isUserLoggedIn()) return | ||
226 | |||
227 | forkJoin([ | ||
228 | this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id), | ||
229 | this.authService.userInformationLoaded.pipe(first()) | ||
230 | ]).subscribe( | ||
231 | ([ existResult ]) => { | ||
232 | const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER) | ||
233 | const existsInWatchLater = existResult[ this.video.id ].find(r => r.playlistId === watchLaterPlaylist.id) | ||
234 | this.inWatchLaterPlaylist = false | ||
235 | |||
236 | this.watchLaterPlaylist = { | ||
237 | id: watchLaterPlaylist.id | ||
238 | } | ||
239 | |||
240 | if (existsInWatchLater) { | ||
241 | this.inWatchLaterPlaylist = true | ||
242 | this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId | ||
243 | } | ||
244 | |||
245 | this.cd.markForCheck() | ||
246 | }) | ||
247 | } | ||
166 | } | 248 | } |
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html index 9679dfefb..c30a43557 100644 --- a/client/src/app/shared/video/video-thumbnail.component.html +++ b/client/src/app/shared/video/video-thumbnail.component.html | |||
@@ -1,18 +1,18 @@ | |||
1 | <a | 1 | <a |
2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name" | 2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name" |
3 | class="video-thumbnail" | 3 | class="video-thumbnail" |
4 | (mouseenter)="load()" (focus)="load()" | ||
5 | > | 4 | > |
6 | <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> | 5 | <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" loading="lazy" /> |
7 | 6 | ||
8 | <div *ngIf="isUserLoggedIn()" class="video-thumbnail-actions-overlay"> | 7 | <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay"> |
9 | <ng-container *ngIf="addedToWatchLater !== true"> | 8 | <ng-container *ngIf="inWatchLaterPlaylist !== true"> |
10 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="addToWatchLater();$event.stopPropagation();false"> | 9 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="onWatchLaterClick($event)"> |
11 | <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon> | 10 | <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon> |
12 | </div> | 11 | </div> |
13 | </ng-container> | 12 | </ng-container> |
14 | <ng-container *ngIf="addedToWatchLater === true"> | 13 | |
15 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="removeFromWatchLater();$event.stopPropagation();false"> | 14 | <ng-container *ngIf="inWatchLaterPlaylist === true"> |
15 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="onWatchLaterClick($event)"> | ||
16 | <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon> | 16 | <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon> |
17 | </div> | 17 | </div> |
18 | </ng-container> | 18 | </ng-container> |
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss index ad221d6ed..573a64987 100644 --- a/client/src/app/shared/video/video-thumbnail.component.scss +++ b/client/src/app/shared/video/video-thumbnail.component.scss | |||
@@ -35,13 +35,6 @@ | |||
35 | bottom: 5px; | 35 | bottom: 5px; |
36 | } | 36 | } |
37 | 37 | ||
38 | &:focus, | ||
39 | &:hover { | ||
40 | .video-thumbnail-actions-overlay { | ||
41 | opacity: 1; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | .video-thumbnail-actions-overlay { | 38 | .video-thumbnail-actions-overlay { |
46 | position: absolute; | 39 | position: absolute; |
47 | display: flex; | 40 | display: flex; |
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts index 6f9292d52..2420ec715 100644 --- a/client/src/app/shared/video/video-thumbnail.component.ts +++ b/client/src/app/shared/video/video-thumbnail.component.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core' | 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' |
2 | import { Video } from './video.model' | 2 | import { Video } from './video.model' |
3 | import { ScreenService } from '@app/shared/misc/screen.service' | 3 | import { ScreenService } from '@app/shared/misc/screen.service' |
4 | import { AuthService, ThemeService } from '@app/core' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { VideoPlaylistService } from '../video-playlist/video-playlist.service' | ||
6 | import { VideoPlaylistElementCreate } from '../../../../../shared' | ||
7 | 5 | ||
8 | @Component({ | 6 | @Component({ |
9 | selector: 'my-video-thumbnail', | 7 | selector: 'my-video-thumbnail', |
@@ -16,45 +14,20 @@ export class VideoThumbnailComponent { | |||
16 | @Input() routerLink: any[] | 14 | @Input() routerLink: any[] |
17 | @Input() queryParams: any[] | 15 | @Input() queryParams: any[] |
18 | 16 | ||
19 | addToWatchLaterText = 'Add to watch later' | 17 | @Input() displayWatchLaterPlaylist: boolean |
20 | addedToWatchLaterText = 'Added to watch later' | 18 | @Input() inWatchLaterPlaylist: boolean |
21 | addedToWatchLater: boolean | ||
22 | 19 | ||
23 | watchLaterPlaylist: any | 20 | @Output() watchLaterClick = new EventEmitter<boolean>() |
21 | |||
22 | addToWatchLaterText: string | ||
23 | addedToWatchLaterText: string | ||
24 | 24 | ||
25 | constructor ( | 25 | constructor ( |
26 | private screenService: ScreenService, | 26 | private screenService: ScreenService, |
27 | private authService: AuthService, | 27 | private i18n: I18n |
28 | private videoPlaylistService: VideoPlaylistService, | 28 | ) { |
29 | private cd: ChangeDetectorRef | 29 | this.addToWatchLaterText = this.i18n('Add to watch later') |
30 | ) {} | 30 | this.addedToWatchLaterText = this.i18n('Remove from watch later') |
31 | |||
32 | load () { | ||
33 | if (this.addedToWatchLater !== undefined) return | ||
34 | if (!this.isUserLoggedIn()) return | ||
35 | |||
36 | this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id) | ||
37 | .subscribe( | ||
38 | existResult => { | ||
39 | for (const playlist of this.authService.getUser().specialPlaylists) { | ||
40 | const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id) | ||
41 | this.addedToWatchLater = !!existingPlaylist | ||
42 | |||
43 | if (existingPlaylist) { | ||
44 | this.watchLaterPlaylist = { | ||
45 | playlistId: existingPlaylist.playlistId, | ||
46 | playlistElementId: existingPlaylist.playlistElementId | ||
47 | } | ||
48 | } else { | ||
49 | this.watchLaterPlaylist = { | ||
50 | playlistId: playlist.id | ||
51 | } | ||
52 | } | ||
53 | |||
54 | this.cd.markForCheck() | ||
55 | } | ||
56 | } | ||
57 | ) | ||
58 | } | 31 | } |
59 | 32 | ||
60 | getImageUrl () { | 33 | getImageUrl () { |
@@ -81,36 +54,10 @@ export class VideoThumbnailComponent { | |||
81 | return [ '/videos/watch', this.video.uuid ] | 54 | return [ '/videos/watch', this.video.uuid ] |
82 | } | 55 | } |
83 | 56 | ||
84 | isUserLoggedIn () { | 57 | onWatchLaterClick (event: Event) { |
85 | return this.authService.isLoggedIn() | 58 | this.watchLaterClick.emit(this.inWatchLaterPlaylist) |
86 | } | ||
87 | |||
88 | addToWatchLater () { | ||
89 | if (this.addedToWatchLater === undefined) return | ||
90 | this.addedToWatchLater = true | ||
91 | |||
92 | this.videoPlaylistService.addVideoInPlaylist( | ||
93 | this.watchLaterPlaylist.playlistId, | ||
94 | { videoId: this.video.id } as VideoPlaylistElementCreate | ||
95 | ).subscribe( | ||
96 | res => { | ||
97 | this.addedToWatchLater = true | ||
98 | this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id | ||
99 | } | ||
100 | ) | ||
101 | } | ||
102 | |||
103 | removeFromWatchLater () { | ||
104 | if (this.addedToWatchLater === undefined) return | ||
105 | this.addedToWatchLater = false | ||
106 | 59 | ||
107 | this.videoPlaylistService.removeVideoFromPlaylist( | 60 | event.stopPropagation() |
108 | this.watchLaterPlaylist.playlistId, | 61 | return false |
109 | this.watchLaterPlaylist.playlistElementId | ||
110 | ).subscribe( | ||
111 | _ => { | ||
112 | this.addedToWatchLater = false | ||
113 | } | ||
114 | ) | ||
115 | } | 62 | } |
116 | } | 63 | } |