diff options
author | kontrollanten <6680299+kontrollanten@users.noreply.github.com> | 2022-10-24 14:48:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-24 14:48:03 +0200 |
commit | 38a3ccc7f8ad0ea94362b58c732af7c387ab46be (patch) | |
tree | ccbd200c4b95d0fcfa56b7d5fc3c9490887187b1 /client/src/app | |
parent | 01a3c07a7913891d4830797403b3865d53f0af61 (diff) | |
download | PeerTube-38a3ccc7f8ad0ea94362b58c732af7c387ab46be.tar.gz PeerTube-38a3ccc7f8ad0ea94362b58c732af7c387ab46be.tar.zst PeerTube-38a3ccc7f8ad0ea94362b58c732af7c387ab46be.zip |
feat: show contained playlists under My videos (#5125)
* feat: show contained playlists under My videos
closes #4769
* refactor(server): remove unused types
* fixes after code review
* fix(client/video-miniature): add to playlist
* fix(server/user/me): shortUUID response
* Revert "fix(client/video-miniature): add to playlist"
This reverts commit f1a0412391c7e2370b87df2594c9fe3f39a40ddc.
* fix(client/PlaylistService): caching
* Revert "fix(server/user/me): shortUUID response"
This reverts commit e3f1ee4e335739b895bced938540c003df24af73.
* Fix fetching playlists
Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'client/src/app')
9 files changed, 46 insertions, 16 deletions
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.html b/client/src/app/+my-library/my-videos/my-videos.component.html index 146dcf41e..995f6b75b 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.html +++ b/client/src/app/+my-library/my-videos/my-videos.component.html | |||
@@ -34,6 +34,7 @@ | |||
34 | </div> | 34 | </div> |
35 | 35 | ||
36 | <my-videos-selection | 36 | <my-videos-selection |
37 | [videosContainedInPlaylists]="videosContainedInPlaylists" | ||
37 | [pagination]="pagination" | 38 | [pagination]="pagination" |
38 | [(selection)]="selection" | 39 | [(selection)]="selection" |
39 | [(videosModel)]="videos" | 40 | [(videosModel)]="videos" |
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index 3ff0ee248..f5262da86 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import { uniqBy } from 'lodash' | ||
1 | import { concat, Observable } from 'rxjs' | 2 | import { concat, Observable } from 'rxjs' |
2 | import { tap, toArray } from 'rxjs/operators' | 3 | import { tap, toArray } from 'rxjs/operators' |
3 | import { Component, OnInit, ViewChild } from '@angular/core' | 4 | import { Component, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 5 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' | 6 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' |
6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 7 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
7 | import { prepareIcu, immutableAssign } from '@app/helpers' | 8 | import { immutableAssign, prepareIcu } from '@app/helpers' |
8 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 9 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 10 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' | 11 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' |
@@ -14,7 +15,8 @@ import { | |||
14 | VideoActionsDisplayType, | 15 | VideoActionsDisplayType, |
15 | VideosSelectionComponent | 16 | VideosSelectionComponent |
16 | } from '@app/shared/shared-video-miniature' | 17 | } from '@app/shared/shared-video-miniature' |
17 | import { VideoChannel, VideoSortField } from '@shared/models' | 18 | import { VideoPlaylistService } from '@app/shared/shared-video-playlist' |
19 | import { VideoChannel, VideoExistInPlaylist, VideosExistInPlaylists, VideoSortField } from '@shared/models' | ||
18 | import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component' | 20 | import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component' |
19 | 21 | ||
20 | @Component({ | 22 | @Component({ |
@@ -26,6 +28,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
26 | @ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent | 28 | @ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent |
27 | @ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent | 29 | @ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent |
28 | 30 | ||
31 | videosContainedInPlaylists: VideosExistInPlaylists = {} | ||
29 | titlePage: string | 32 | titlePage: string |
30 | selection: SelectionType = {} | 33 | selection: SelectionType = {} |
31 | pagination: ComponentPagination = { | 34 | pagination: ComponentPagination = { |
@@ -83,7 +86,8 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
83 | protected notifier: Notifier, | 86 | protected notifier: Notifier, |
84 | protected screenService: ScreenService, | 87 | protected screenService: ScreenService, |
85 | private confirmService: ConfirmService, | 88 | private confirmService: ConfirmService, |
86 | private videoService: VideoService | 89 | private videoService: VideoService, |
90 | private playlistService: VideoPlaylistService | ||
87 | ) { | 91 | ) { |
88 | this.titlePage = $localize`My videos` | 92 | this.titlePage = $localize`My videos` |
89 | } | 93 | } |
@@ -156,10 +160,20 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
156 | sort: this.sort, | 160 | sort: this.sort, |
157 | userChannels: this.userChannels, | 161 | userChannels: this.userChannels, |
158 | search: this.search | 162 | search: this.search |
159 | }) | 163 | }).pipe( |
160 | .pipe( | 164 | tap(res => this.pagination.totalItems = res.total), |
161 | tap(res => this.pagination.totalItems = res.total) | 165 | tap(({ data }) => this.fetchVideosContainedInPlaylists(data)) |
162 | ) | 166 | ) |
167 | } | ||
168 | |||
169 | private fetchVideosContainedInPlaylists (videos: Video[]) { | ||
170 | this.playlistService.doVideosExistInPlaylist(videos.map(v => v.id)) | ||
171 | .subscribe(result => { | ||
172 | this.videosContainedInPlaylists = Object.keys(result).reduce((acc, videoId) => ({ | ||
173 | ...acc, | ||
174 | [videoId]: uniqBy(result[videoId], (p: VideoExistInPlaylist) => p.playlistId) | ||
175 | }), this.videosContainedInPlaylists) | ||
176 | }) | ||
163 | } | 177 | } |
164 | 178 | ||
165 | async deleteSelectedVideos () { | 179 | async deleteSelectedVideos () { |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html index e8d2ca1c4..6fdf24b2d 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html | |||
@@ -52,6 +52,12 @@ | |||
52 | <ng-container *ngIf="displayOptions.privacyText && displayOptions.state && getStateLabel(video)"> - </ng-container> | 52 | <ng-container *ngIf="displayOptions.privacyText && displayOptions.state && getStateLabel(video)"> - </ng-container> |
53 | <ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container> | 53 | <ng-container *ngIf="displayOptions.state">{{ getStateLabel(video) }}</ng-container> |
54 | </div> | 54 | </div> |
55 | |||
56 | <div *ngIf="containedInPlaylists" class="video-contained-in-playlists"> | ||
57 | <a *ngFor="let playlist of containedInPlaylists" class="chip rectangular bg-secondary text-light" [routerLink]="['/w/p/', playlist.playlistShortUUID]"> | ||
58 | {{ playlist.playlistDisplayName }} | ||
59 | </a> | ||
60 | </div> | ||
55 | </div> | 61 | </div> |
56 | </div> | 62 | </div> |
57 | 63 | ||
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss index a397efdca..ba2adfc5a 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss | |||
@@ -4,6 +4,10 @@ | |||
4 | 4 | ||
5 | $more-button-width: 40px; | 5 | $more-button-width: 40px; |
6 | 6 | ||
7 | .chip { | ||
8 | @include chip; | ||
9 | } | ||
10 | |||
7 | .video-miniature { | 11 | .video-miniature { |
8 | font-size: 14px; | 12 | font-size: 14px; |
9 | } | 13 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index 20596d6d0..85c63c173 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -11,7 +11,7 @@ import { | |||
11 | Output | 11 | Output |
12 | } from '@angular/core' | 12 | } from '@angular/core' |
13 | import { AuthService, ScreenService, ServerService, User } from '@app/core' | 13 | import { AuthService, ScreenService, ServerService, User } from '@app/core' |
14 | import { HTMLServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models' | 14 | import { HTMLServerConfig, VideoExistInPlaylist, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models' |
15 | import { LinkType } from '../../../types/link.type' | 15 | import { LinkType } from '../../../types/link.type' |
16 | import { ActorAvatarSize } from '../shared-actor-image/actor-avatar.component' | 16 | import { ActorAvatarSize } from '../shared-actor-image/actor-avatar.component' |
17 | import { Video } from '../shared-main' | 17 | import { Video } from '../shared-main' |
@@ -40,6 +40,7 @@ export type MiniatureDisplayOptions = { | |||
40 | export class VideoMiniatureComponent implements OnInit { | 40 | export class VideoMiniatureComponent implements OnInit { |
41 | @Input() user: User | 41 | @Input() user: User |
42 | @Input() video: Video | 42 | @Input() video: Video |
43 | @Input() containedInPlaylists: VideoExistInPlaylist[] | ||
43 | 44 | ||
44 | @Input() displayOptions: MiniatureDisplayOptions = { | 45 | @Input() displayOptions: MiniatureDisplayOptions = { |
45 | date: true, | 46 | date: true, |
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.html b/client/src/app/shared/shared-video-miniature/videos-selection.component.html index 6ea2661e4..6c6db4b96 100644 --- a/client/src/app/shared/shared-video-miniature/videos-selection.component.html +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.html | |||
@@ -12,6 +12,7 @@ | |||
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <my-video-miniature | 14 | <my-video-miniature |
15 | [containedInPlaylists]="videosContainedInPlaylists ? videosContainedInPlaylists[video.id] : undefined" | ||
15 | [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions" | 16 | [video]="video" [displayAsRow]="true" [displayOptions]="miniatureDisplayOptions" |
16 | [displayVideoActions]="false" [user]="user" | 17 | [displayVideoActions]="false" [user]="user" |
17 | ></my-video-miniature> | 18 | ></my-video-miniature> |
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts index fa3c79bbb..460a0080e 100644 --- a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts | |||
@@ -2,7 +2,7 @@ import { Observable, Subject } from 'rxjs' | |||
2 | import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core' | 2 | import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core' |
3 | import { ComponentPagination, Notifier, User } from '@app/core' | 3 | import { ComponentPagination, Notifier, User } from '@app/core' |
4 | import { logger } from '@root-helpers/logger' | 4 | import { logger } from '@root-helpers/logger' |
5 | import { ResultList, VideoSortField } from '@shared/models' | 5 | import { ResultList, VideosExistInPlaylists, VideoSortField } from '@shared/models' |
6 | import { PeerTubeTemplateDirective, Video } from '../shared-main' | 6 | import { PeerTubeTemplateDirective, Video } from '../shared-main' |
7 | import { MiniatureDisplayOptions } from './video-miniature.component' | 7 | import { MiniatureDisplayOptions } from './video-miniature.component' |
8 | 8 | ||
@@ -14,6 +14,7 @@ export type SelectionType = { [ id: number ]: boolean } | |||
14 | styleUrls: [ './videos-selection.component.scss' ] | 14 | styleUrls: [ './videos-selection.component.scss' ] |
15 | }) | 15 | }) |
16 | export class VideosSelectionComponent implements AfterContentInit { | 16 | export class VideosSelectionComponent implements AfterContentInit { |
17 | @Input() videosContainedInPlaylists: VideosExistInPlaylists | ||
17 | @Input() user: User | 18 | @Input() user: User |
18 | @Input() pagination: ComponentPagination | 19 | @Input() pagination: ComponentPagination |
19 | 20 | ||
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts index f81de7c6b..2fc39fc75 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts | |||
@@ -6,8 +6,8 @@ import { AuthService, DisableForReuseHook, Notifier } from '@app/core' | |||
6 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' | 6 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' |
7 | import { secondsToTime } from '@shared/core-utils' | 7 | import { secondsToTime } from '@shared/core-utils' |
8 | import { | 8 | import { |
9 | CachedVideoExistInPlaylist, | ||
9 | Video, | 10 | Video, |
10 | VideoExistInPlaylist, | ||
11 | VideoPlaylistCreate, | 11 | VideoPlaylistCreate, |
12 | VideoPlaylistElementCreate, | 12 | VideoPlaylistElementCreate, |
13 | VideoPlaylistElementUpdate, | 13 | VideoPlaylistElementUpdate, |
@@ -330,7 +330,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
330 | } | 330 | } |
331 | } | 331 | } |
332 | 332 | ||
333 | private rebuildPlaylists (existResult: VideoExistInPlaylist[]) { | 333 | private rebuildPlaylists (existResult: CachedVideoExistInPlaylist[]) { |
334 | debugLogger('Got existing results for %d.', this.video.id, existResult) | 334 | debugLogger('Got existing results for %d.', this.video.id, existResult) |
335 | 335 | ||
336 | const oldPlaylists = this.videoPlaylists | 336 | const oldPlaylists = this.videoPlaylists |
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts index d71f8f72e..330a51f91 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts | |||
@@ -8,6 +8,8 @@ import { buildBulkObservable, objectToFormData } from '@app/helpers' | |||
8 | import { Account, AccountService, VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 8 | import { Account, AccountService, VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
9 | import { NGX_LOADING_BAR_IGNORED } from '@ngx-loading-bar/http-client' | 9 | import { NGX_LOADING_BAR_IGNORED } from '@ngx-loading-bar/http-client' |
10 | import { | 10 | import { |
11 | CachedVideoExistInPlaylist, | ||
12 | CachedVideosExistInPlaylists, | ||
11 | ResultList, | 13 | ResultList, |
12 | VideoExistInPlaylist, | 14 | VideoExistInPlaylist, |
13 | VideoPlaylist as VideoPlaylistServerModel, | 15 | VideoPlaylist as VideoPlaylistServerModel, |
@@ -34,11 +36,11 @@ export class VideoPlaylistService { | |||
34 | 36 | ||
35 | // Use a replay subject because we "next" a value before subscribing | 37 | // Use a replay subject because we "next" a value before subscribing |
36 | private videoExistsInPlaylistNotifier = new ReplaySubject<number>(1) | 38 | private videoExistsInPlaylistNotifier = new ReplaySubject<number>(1) |
37 | private videoExistsInPlaylistCacheSubject = new Subject<VideosExistInPlaylists>() | 39 | private videoExistsInPlaylistCacheSubject = new Subject<CachedVideosExistInPlaylists>() |
38 | private readonly videoExistsInPlaylistObservable: Observable<VideosExistInPlaylists> | 40 | private readonly videoExistsInPlaylistObservable: Observable<CachedVideosExistInPlaylists> |
39 | 41 | ||
40 | private videoExistsObservableCache: { [ id: number ]: Observable<VideoExistInPlaylist[]> } = {} | 42 | private videoExistsObservableCache: { [ id: number ]: Observable<CachedVideoExistInPlaylist[]> } = {} |
41 | private videoExistsCache: { [ id: number ]: VideoExistInPlaylist[] } = {} | 43 | private videoExistsCache: { [ id: number ]: CachedVideoExistInPlaylist[] } = {} |
42 | 44 | ||
43 | private myAccountPlaylistCache: ResultList<CachedPlaylist> = undefined | 45 | private myAccountPlaylistCache: ResultList<CachedPlaylist> = undefined |
44 | private myAccountPlaylistCacheRunning: Observable<ResultList<CachedPlaylist>> | 46 | private myAccountPlaylistCacheRunning: Observable<ResultList<CachedPlaylist>> |
@@ -346,7 +348,7 @@ export class VideoPlaylistService { | |||
346 | ) | 348 | ) |
347 | } | 349 | } |
348 | 350 | ||
349 | private doVideosExistInPlaylist (videoIds: number[]): Observable<VideosExistInPlaylists> { | 351 | doVideosExistInPlaylist (videoIds: number[]): Observable<VideosExistInPlaylists> { |
350 | const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist' | 352 | const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist' |
351 | 353 | ||
352 | let params = new HttpParams() | 354 | let params = new HttpParams() |