diff options
26 files changed, 248 insertions, 45 deletions
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 3f921b13f..07557a029 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts | |||
@@ -85,20 +85,20 @@ const myAccountRoutes: Routes = [ | |||
85 | } | 85 | } |
86 | }, | 86 | }, |
87 | { | 87 | { |
88 | path: 'video-playlists/:videoPlaylistId', | 88 | path: 'video-playlists/create', |
89 | component: MyAccountVideoPlaylistElementsComponent, | 89 | component: MyAccountVideoPlaylistCreateComponent, |
90 | data: { | 90 | data: { |
91 | meta: { | 91 | meta: { |
92 | title: 'Playlist elements' | 92 | title: 'Create new playlist' |
93 | } | 93 | } |
94 | } | 94 | } |
95 | }, | 95 | }, |
96 | { | 96 | { |
97 | path: 'video-playlists/create', | 97 | path: 'video-playlists/:videoPlaylistId', |
98 | component: MyAccountVideoPlaylistCreateComponent, | 98 | component: MyAccountVideoPlaylistElementsComponent, |
99 | data: { | 99 | data: { |
100 | meta: { | 100 | meta: { |
101 | title: 'Create new playlist' | 101 | title: 'Playlist elements' |
102 | } | 102 | } |
103 | } | 103 | } |
104 | }, | 104 | }, |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html index bc26e198e..95d4519fa 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html | |||
@@ -1,11 +1,26 @@ | |||
1 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">No videos in this playlist.</div> | 1 | <div class="row"> |
2 | 2 | ||
3 | <div | 3 | <div class="playlist-info col-xs-12 col-md-5 col-xl-3"> |
4 | class="videos" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" | 4 | <my-video-playlist-miniature |
5 | cdkDropList (cdkDropListDropped)="drop($event)" | 5 | *ngIf="playlist" [playlist]="playlist" [toManage]="false" [displayChannel]="true" |
6 | > | 6 | [displayDescription]="true" [displayPrivacy]="true" |
7 | <div class="video" *ngFor="let video of videos" cdkDrag (cdkDragMoved)="onDragMove($event)"> | 7 | ></my-video-playlist-miniature> |
8 | <my-video-playlist-element-miniature [video]="video" [playlist]="playlist" [owned]="true" (elementRemoved)="onElementRemoved($event)"> | 8 | </div> |
9 | </my-video-playlist-element-miniature> | 9 | |
10 | <div class="col-xs-12 col-md-7 col-xl-9"> | ||
11 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">No videos in this playlist.</div> | ||
12 | |||
13 | <div | ||
14 | class="videos" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" | ||
15 | cdkDropList (cdkDropListDropped)="drop($event)" | ||
16 | > | ||
17 | <div class="video" *ngFor="let video of videos; trackBy: trackByFn" cdkDrag (cdkDragMoved)="onDragMove($event)"> | ||
18 | <my-video-playlist-element-miniature | ||
19 | [video]="video" [playlist]="playlist" [owned]="true" (elementRemoved)="onElementRemoved($event)" | ||
20 | [position]="video.playlistElement.position" | ||
21 | > | ||
22 | </my-video-playlist-element-miniature> | ||
23 | </div> | ||
24 | </div> | ||
10 | </div> | 25 | </div> |
11 | </div> | 26 | </div> |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss index b05af0490..900669827 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss | |||
@@ -2,6 +2,17 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import '_miniature'; | 3 | @import '_miniature'; |
4 | 4 | ||
5 | .playlist-info { | ||
6 | background-color: var(--submenuColor); | ||
7 | margin-left: -15px; | ||
8 | margin-top: -$sub-menu-margin-bottom; | ||
9 | |||
10 | padding: $sub-menu-margin-bottom 0; | ||
11 | |||
12 | display: flex; | ||
13 | justify-content: center; | ||
14 | } | ||
15 | |||
5 | // Thanks Angular CDK <3 https://material.angular.io/cdk/drag-drop/examples | 16 | // Thanks Angular CDK <3 https://material.angular.io/cdk/drag-drop/examples |
6 | .cdk-drag-preview { | 17 | .cdk-drag-preview { |
7 | box-sizing: border-box; | 18 | box-sizing: border-box; |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts index dcf470be3..25d51d2cb 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts | |||
@@ -24,7 +24,7 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
24 | 24 | ||
25 | pagination: ComponentPagination = { | 25 | pagination: ComponentPagination = { |
26 | currentPage: 1, | 26 | currentPage: 1, |
27 | itemsPerPage: 10, | 27 | itemsPerPage: 30, |
28 | totalItems: null | 28 | totalItems: null |
29 | } | 29 | } |
30 | 30 | ||
@@ -123,6 +123,10 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro | |||
123 | this.loadElements() | 123 | this.loadElements() |
124 | } | 124 | } |
125 | 125 | ||
126 | trackByFn (index: number, elem: Video) { | ||
127 | return elem.id | ||
128 | } | ||
129 | |||
126 | private loadElements () { | 130 | private loadElements () { |
127 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) | 131 | this.videoService.getPlaylistVideos(this.videoPlaylistId, this.pagination) |
128 | .subscribe(({ totalVideos, videos }) => { | 132 | .subscribe(({ totalVideos, videos }) => { |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html index 7d1bed12a..322560673 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html | |||
@@ -8,7 +8,8 @@ | |||
8 | <div class="video-playlists" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> | 8 | <div class="video-playlists" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> |
9 | <div *ngFor="let playlist of videoPlaylists" class="video-playlist"> | 9 | <div *ngFor="let playlist of videoPlaylists" class="video-playlist"> |
10 | <div class="miniature-wrapper"> | 10 | <div class="miniature-wrapper"> |
11 | <my-video-playlist-miniature [playlist]="playlist" [toManage]="true"></my-video-playlist-miniature> | 11 | <my-video-playlist-miniature [playlist]="playlist" [toManage]="true" [displayChannel]="true" [displayDescription]="true" [displayPrivacy]="true" |
12 | ></my-video-playlist-miniature> | ||
12 | </div> | 13 | </div> |
13 | 14 | ||
14 | <div *ngIf="isRegularPlaylist(playlist)" class="video-playlist-buttons"> | 15 | <div *ngIf="isRegularPlaylist(playlist)" class="video-playlist-buttons"> |
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss index 88fba5b05..f648c33e4 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss | |||
@@ -20,8 +20,9 @@ | |||
20 | /deep/ .miniature { | 20 | /deep/ .miniature { |
21 | display: flex; | 21 | display: flex; |
22 | 22 | ||
23 | .miniature-bottom { | 23 | .miniature-info { |
24 | margin-left: 10px; | 24 | margin-left: 10px; |
25 | width: auto; | ||
25 | } | 26 | } |
26 | } | 27 | } |
27 | } | 28 | } |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html index 69748ef37..b09e845ac 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html | |||
@@ -17,7 +17,7 @@ | |||
17 | <div class="video-info"> | 17 | <div class="video-info"> |
18 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> | 18 | <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> |
19 | <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> | 19 | <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> |
20 | <div class="video-info-private">{{ video.privacy.label }}{{ getStateLabel(video) }}</div> | 20 | <div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div> |
21 | <div *ngIf="video.blacklisted" class="video-info-blacklisted"> | 21 | <div *ngIf="video.blacklisted" class="video-info-blacklisted"> |
22 | <span class="blacklisted-label" i18n>Blacklisted</span> | 22 | <span class="blacklisted-label" i18n>Blacklisted</span> |
23 | <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span> | 23 | <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span> |
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 39d0cf2f7..f6b5faa45 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss | |||
@@ -64,11 +64,11 @@ | |||
64 | } | 64 | } |
65 | 65 | ||
66 | .video-info-date-views, | 66 | .video-info-date-views, |
67 | .video-info-private, | 67 | .video-info-privacy, |
68 | .video-info-blacklisted { | 68 | .video-info-blacklisted { |
69 | font-size: 13px; | 69 | font-size: 13px; |
70 | 70 | ||
71 | &.video-info-private, | 71 | &.video-info-privacy, |
72 | &.video-info-blacklisted .blacklisted-label { | 72 | &.video-info-blacklisted .blacklisted-label { |
73 | font-weight: $font-semibold; | 73 | font-weight: $font-semibold; |
74 | } | 74 | } |
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html new file mode 100644 index 000000000..0d9fba375 --- /dev/null +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.html | |||
@@ -0,0 +1,11 @@ | |||
1 | <div i18n class="title-page title-page-single"> | ||
2 | Created {{pagination.totalItems}} playlists | ||
3 | </div> | ||
4 | |||
5 | <div i18n class="no-results" *ngIf="pagination.totalItems === 0">This channel does not have playlists.</div> | ||
6 | |||
7 | <div class="video-playlist" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> | ||
8 | <div *ngFor="let playlist of videoPlaylists"> | ||
9 | <my-video-playlist-miniature [playlist]="playlist" [toManage]="false"></my-video-playlist-miniature> | ||
10 | </div> | ||
11 | </div> | ||
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.scss b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.scss new file mode 100644 index 000000000..fe9104794 --- /dev/null +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.scss | |||
@@ -0,0 +1,9 @@ | |||
1 | .video-playlist { | ||
2 | display: flex; | ||
3 | justify-content: center; | ||
4 | |||
5 | my-video-playlist-miniature { | ||
6 | margin-right: 15px; | ||
7 | margin-bottom: 30px; | ||
8 | } | ||
9 | } | ||
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts new file mode 100644 index 000000000..f878a5a24 --- /dev/null +++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts | |||
@@ -0,0 +1,67 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { AuthService } from '../../core/auth' | ||
3 | import { ConfirmService } from '../../core/confirm' | ||
4 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
6 | import { flatMap } from 'rxjs/operators' | ||
7 | import { Subscription } from 'rxjs' | ||
8 | import { Notifier } from '@app/core' | ||
9 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
10 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
11 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
12 | |||
13 | @Component({ | ||
14 | selector: 'my-video-channel-playlists', | ||
15 | templateUrl: './video-channel-playlists.component.html', | ||
16 | styleUrls: [ './video-channel-playlists.component.scss' ] | ||
17 | }) | ||
18 | export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy { | ||
19 | videoPlaylists: VideoPlaylist[] = [] | ||
20 | |||
21 | pagination: ComponentPagination = { | ||
22 | currentPage: 1, | ||
23 | itemsPerPage: 20, | ||
24 | totalItems: null | ||
25 | } | ||
26 | |||
27 | private videoChannelSub: Subscription | ||
28 | private videoChannel: VideoChannel | ||
29 | |||
30 | constructor ( | ||
31 | private authService: AuthService, | ||
32 | private notifier: Notifier, | ||
33 | private confirmService: ConfirmService, | ||
34 | private videoPlaylistService: VideoPlaylistService, | ||
35 | private videoChannelService: VideoChannelService | ||
36 | ) {} | ||
37 | |||
38 | ngOnInit () { | ||
39 | // Parent get the video channel for us | ||
40 | this.videoChannelSub = this.videoChannelService.videoChannelLoaded | ||
41 | .subscribe(videoChannel => { | ||
42 | this.videoChannel = videoChannel | ||
43 | this.loadVideoPlaylists() | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | ngOnDestroy () { | ||
48 | if (this.videoChannelSub) this.videoChannelSub.unsubscribe() | ||
49 | } | ||
50 | |||
51 | onNearOfBottom () { | ||
52 | // Last page | ||
53 | if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return | ||
54 | |||
55 | this.pagination.currentPage += 1 | ||
56 | this.loadVideoPlaylists() | ||
57 | } | ||
58 | |||
59 | private loadVideoPlaylists () { | ||
60 | this.authService.userInformationLoaded | ||
61 | .pipe(flatMap(() => this.videoPlaylistService.listChannelPlaylists(this.videoChannel))) | ||
62 | .subscribe(res => { | ||
63 | this.videoPlaylists = this.videoPlaylists.concat(res.data) | ||
64 | this.pagination.totalItems = res.total | ||
65 | }) | ||
66 | } | ||
67 | } | ||
diff --git a/client/src/app/+video-channels/video-channels-routing.module.ts b/client/src/app/+video-channels/video-channels-routing.module.ts index 3ac3533d9..cedd07d39 100644 --- a/client/src/app/+video-channels/video-channels-routing.module.ts +++ b/client/src/app/+video-channels/video-channels-routing.module.ts | |||
@@ -4,6 +4,7 @@ import { MetaGuard } from '@ngx-meta/core' | |||
4 | import { VideoChannelsComponent } from './video-channels.component' | 4 | import { VideoChannelsComponent } from './video-channels.component' |
5 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' | 5 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' |
6 | import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component' | 6 | import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component' |
7 | import { VideoChannelPlaylistsComponent } from '@app/+video-channels/video-channel-playlists/video-channel-playlists.component' | ||
7 | 8 | ||
8 | const videoChannelsRoutes: Routes = [ | 9 | const videoChannelsRoutes: Routes = [ |
9 | { | 10 | { |
@@ -26,6 +27,15 @@ const videoChannelsRoutes: Routes = [ | |||
26 | } | 27 | } |
27 | }, | 28 | }, |
28 | { | 29 | { |
30 | path: 'video-playlists', | ||
31 | component: VideoChannelPlaylistsComponent, | ||
32 | data: { | ||
33 | meta: { | ||
34 | title: 'Video channel playlists' | ||
35 | } | ||
36 | } | ||
37 | }, | ||
38 | { | ||
29 | path: 'about', | 39 | path: 'about', |
30 | component: VideoChannelAboutComponent, | 40 | component: VideoChannelAboutComponent, |
31 | data: { | 41 | data: { |
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html index c65b5713d..600b7a365 100644 --- a/client/src/app/+video-channels/video-channels.component.html +++ b/client/src/app/+video-channels/video-channels.component.html | |||
@@ -22,6 +22,7 @@ | |||
22 | 22 | ||
23 | <div class="links"> | 23 | <div class="links"> |
24 | <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> | 24 | <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> |
25 | <a i18n routerLink="video-playlists" routerLinkActive="active" class="title-page">Video playlists</a> | ||
25 | <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> | 26 | <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> |
26 | </div> | 27 | </div> |
27 | </div> | 28 | </div> |
diff --git a/client/src/app/+video-channels/video-channels.module.ts b/client/src/app/+video-channels/video-channels.module.ts index a09ea6f11..6975d05b2 100644 --- a/client/src/app/+video-channels/video-channels.module.ts +++ b/client/src/app/+video-channels/video-channels.module.ts | |||
@@ -4,6 +4,7 @@ import { VideoChannelsRoutingModule } from './video-channels-routing.module' | |||
4 | import { VideoChannelsComponent } from './video-channels.component' | 4 | import { VideoChannelsComponent } from './video-channels.component' |
5 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' | 5 | import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' |
6 | import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component' | 6 | import { VideoChannelAboutComponent } from './video-channel-about/video-channel-about.component' |
7 | import { VideoChannelPlaylistsComponent } from '@app/+video-channels/video-channel-playlists/video-channel-playlists.component' | ||
7 | 8 | ||
8 | @NgModule({ | 9 | @NgModule({ |
9 | imports: [ | 10 | imports: [ |
@@ -14,7 +15,8 @@ import { VideoChannelAboutComponent } from './video-channel-about/video-channel- | |||
14 | declarations: [ | 15 | declarations: [ |
15 | VideoChannelsComponent, | 16 | VideoChannelsComponent, |
16 | VideoChannelVideosComponent, | 17 | VideoChannelVideosComponent, |
17 | VideoChannelAboutComponent | 18 | VideoChannelAboutComponent, |
19 | VideoChannelPlaylistsComponent | ||
18 | ], | 20 | ], |
19 | 21 | ||
20 | exports: [ | 22 | exports: [ |
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html index 1f178675f..4764fc0e1 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html +++ b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | <a [routerLink]="buildRouterLink()" [queryParams]="buildRouterQuery()"> | 2 | <a [routerLink]="buildRouterLink()" [queryParams]="buildRouterQuery()"> |
3 | <div class="position"> | 3 | <div class="position"> |
4 | <my-global-icon *ngIf="playing" iconName="play"></my-global-icon> | 4 | <my-global-icon *ngIf="playing" iconName="play"></my-global-icon> |
5 | <ng-container *ngIf="!playing">{{ video.playlistElement.position }}</ng-container> | 5 | <ng-container *ngIf="!playing">{{ position }}</ng-container> |
6 | </div> | 6 | </div> |
7 | 7 | ||
8 | <my-video-thumbnail | 8 | <my-video-thumbnail |
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss index eb869f69a..f57fd2e1c 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss +++ b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss | |||
@@ -34,7 +34,7 @@ | |||
34 | font-weight: $font-semibold; | 34 | font-weight: $font-semibold; |
35 | margin-right: 10px; | 35 | margin-right: 10px; |
36 | color: $grey-foreground-color; | 36 | color: $grey-foreground-color; |
37 | min-width: 20px; | 37 | min-width: 25px; |
38 | 38 | ||
39 | my-global-icon { | 39 | my-global-icon { |
40 | @include apply-svg-color($grey-foreground-color); | 40 | @include apply-svg-color($grey-foreground-color); |
@@ -59,7 +59,7 @@ | |||
59 | 59 | ||
60 | a { | 60 | a { |
61 | color: var(--mainForegroundColor); | 61 | color: var(--mainForegroundColor); |
62 | width: fit-content; | 62 | width: auto; |
63 | 63 | ||
64 | &:hover { | 64 | &:hover { |
65 | text-decoration: underline !important; | 65 | text-decoration: underline !important; |
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts index c0cfd855d..6cc5b87b4 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core' | 1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' |
2 | import { Video } from '@app/shared/video/video.model' | 2 | import { Video } from '@app/shared/video/video.model' |
3 | import { VideoPlaylistElementUpdate } from '@shared/models' | 3 | import { VideoPlaylistElementUpdate } from '@shared/models' |
4 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' | 4 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' |
@@ -13,7 +13,8 @@ import { secondsToTime } from '../../../assets/player/utils' | |||
13 | @Component({ | 13 | @Component({ |
14 | selector: 'my-video-playlist-element-miniature', | 14 | selector: 'my-video-playlist-element-miniature', |
15 | styleUrls: [ './video-playlist-element-miniature.component.scss' ], | 15 | styleUrls: [ './video-playlist-element-miniature.component.scss' ], |
16 | templateUrl: './video-playlist-element-miniature.component.html' | 16 | templateUrl: './video-playlist-element-miniature.component.html', |
17 | changeDetection: ChangeDetectionStrategy.OnPush | ||
17 | }) | 18 | }) |
18 | export class VideoPlaylistElementMiniatureComponent { | 19 | export class VideoPlaylistElementMiniatureComponent { |
19 | @ViewChild('moreDropdown') moreDropdown: NgbDropdown | 20 | @ViewChild('moreDropdown') moreDropdown: NgbDropdown |
@@ -24,6 +25,7 @@ export class VideoPlaylistElementMiniatureComponent { | |||
24 | @Input() playing = false | 25 | @Input() playing = false |
25 | @Input() rowLink = false | 26 | @Input() rowLink = false |
26 | @Input() accountLink = true | 27 | @Input() accountLink = true |
28 | @Input() position: number | ||
27 | 29 | ||
28 | @Output() elementRemoved = new EventEmitter<Video>() | 30 | @Output() elementRemoved = new EventEmitter<Video>() |
29 | 31 | ||
@@ -44,7 +46,8 @@ export class VideoPlaylistElementMiniatureComponent { | |||
44 | private route: ActivatedRoute, | 46 | private route: ActivatedRoute, |
45 | private i18n: I18n, | 47 | private i18n: I18n, |
46 | private videoService: VideoService, | 48 | private videoService: VideoService, |
47 | private videoPlaylistService: VideoPlaylistService | 49 | private videoPlaylistService: VideoPlaylistService, |
50 | private cdr: ChangeDetectorRef | ||
48 | ) {} | 51 | ) {} |
49 | 52 | ||
50 | buildRouterLink () { | 53 | buildRouterLink () { |
@@ -95,6 +98,8 @@ export class VideoPlaylistElementMiniatureComponent { | |||
95 | 98 | ||
96 | video.playlistElement.startTimestamp = body.startTimestamp | 99 | video.playlistElement.startTimestamp = body.startTimestamp |
97 | video.playlistElement.stopTimestamp = body.stopTimestamp | 100 | video.playlistElement.stopTimestamp = body.stopTimestamp |
101 | |||
102 | this.cdr.detectChanges() | ||
98 | }, | 103 | }, |
99 | 104 | ||
100 | err => this.notifier.error(err.message) | 105 | err => this.notifier.error(err.message) |
@@ -145,5 +150,10 @@ export class VideoPlaylistElementMiniatureComponent { | |||
145 | this.timestampOptions.stopTimestamp = video.playlistElement.stopTimestamp | 150 | this.timestampOptions.stopTimestamp = video.playlistElement.stopTimestamp |
146 | } | 151 | } |
147 | } | 152 | } |
153 | |||
154 | // FIXME: why do we have to use setTimeout here? | ||
155 | setTimeout(() => { | ||
156 | this.cdr.detectChanges() | ||
157 | }) | ||
148 | } | 158 | } |
149 | } | 159 | } |
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html index a136f9233..c01c73012 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="miniature" [ngClass]="{ 'no-videos': playlist.videosLength === 0, 'to-manage': toManage }"> | 1 | <div class="miniature" [ngClass]="{ 'no-videos': playlist.videosLength === 0, 'to-manage': toManage }"> |
2 | <a | 2 | <a |
3 | [routerLink]="getPlaylistUrl()" [attr.title]="playlist.displayName" | 3 | [routerLink]="getPlaylistUrl()" [attr.title]="playlist.description" |
4 | class="miniature-thumbnail" | 4 | class="miniature-thumbnail" |
5 | > | 5 | > |
6 | <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> | 6 | <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> |
@@ -14,9 +14,21 @@ | |||
14 | </div> | 14 | </div> |
15 | </a> | 15 | </a> |
16 | 16 | ||
17 | <div class="miniature-bottom"> | 17 | <div class="miniature-info"> |
18 | <a tabindex="-1" class="miniature-name" [routerLink]="getPlaylistUrl()" [attr.title]="playlist.displayName"> | 18 | <a tabindex="-1" class="miniature-name" [routerLink]="getPlaylistUrl()" [attr.title]="playlist.description"> |
19 | {{ playlist.displayName }} | 19 | {{ playlist.displayName }} |
20 | </a> | 20 | </a> |
21 | |||
22 | <div class="video-info-privacy" *ngIf="displayPrivacy">{{ playlist.privacy.label }}</div> | ||
23 | |||
24 | <div class="video-info-by-date"> | ||
25 | <a i18n [routerLink]="[ '/video-channels', playlist.videoChannelBy ]" class="by" *ngIf="displayChannel && playlist.videoChannelBy"> | ||
26 | {{ playlist.videoChannelBy }} | ||
27 | </a> | ||
28 | |||
29 | <div i18n class="updated-at">Updated {{ playlist.updatedAt | myFromNow }}</div> | ||
30 | </div> | ||
31 | |||
32 | <div *ngIf="displayDescription" class="video-info-description">{{ playlist.description }}</div> | ||
21 | </div> | 33 | </div> |
22 | </div> | 34 | </div> |
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss index 72158eb10..94edd1177 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss | |||
@@ -11,9 +11,11 @@ | |||
11 | } | 11 | } |
12 | } | 12 | } |
13 | 13 | ||
14 | &.to-manage .play-overlay, | 14 | &.to-manage, |
15 | &.no-videos { | 15 | &.no-videos { |
16 | display: none; | 16 | .play-overlay { |
17 | display: none; | ||
18 | } | ||
17 | } | 19 | } |
18 | 20 | ||
19 | .miniature-thumbnail { | 21 | .miniature-thumbnail { |
@@ -34,7 +36,7 @@ | |||
34 | } | 36 | } |
35 | } | 37 | } |
36 | 38 | ||
37 | .miniature-bottom { | 39 | .miniature-info { |
38 | width: 200px; | 40 | width: 200px; |
39 | margin-top: 2px; | 41 | margin-top: 2px; |
40 | line-height: normal; | 42 | line-height: normal; |
@@ -42,5 +44,33 @@ | |||
42 | .miniature-name { | 44 | .miniature-name { |
43 | @include miniature-name; | 45 | @include miniature-name; |
44 | } | 46 | } |
47 | |||
48 | .video-info-by-date { | ||
49 | display: flex; | ||
50 | font-size: 13px; | ||
51 | margin: 5px 0; | ||
52 | |||
53 | .by { | ||
54 | @include disable-default-a-behaviour; | ||
55 | |||
56 | display: block; | ||
57 | color: var(--mainForegroundColor); | ||
58 | |||
59 | &::after { | ||
60 | content: '-'; | ||
61 | margin: 0 3px; | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | .video-info-privacy { | ||
67 | font-size: 13px; | ||
68 | font-weight: $font-semibold; | ||
69 | } | ||
70 | |||
71 | .video-info-description { | ||
72 | margin-top: 10px; | ||
73 | color: $grey-foreground-color; | ||
74 | } | ||
45 | } | 75 | } |
46 | } | 76 | } |
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts index cb5803400..523e96f2a 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts | |||
@@ -9,6 +9,9 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | |||
9 | export class VideoPlaylistMiniatureComponent { | 9 | export class VideoPlaylistMiniatureComponent { |
10 | @Input() playlist: VideoPlaylist | 10 | @Input() playlist: VideoPlaylist |
11 | @Input() toManage = false | 11 | @Input() toManage = false |
12 | @Input() displayChannel = false | ||
13 | @Input() displayDescription = false | ||
14 | @Input() displayPrivacy = false | ||
12 | 15 | ||
13 | getPlaylistUrl () { | 16 | getPlaylistUrl () { |
14 | if (this.toManage) return [ '/my-account/video-playlists', this.playlist.uuid ] | 17 | if (this.toManage) return [ '/my-account/video-playlists', this.playlist.uuid ] |
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts index 186597a3a..a9e75007c 100644 --- a/client/src/app/shared/video/infinite-scroller.directive.ts +++ b/client/src/app/shared/video/infinite-scroller.directive.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { distinct, distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' | 1 | import { distinct, distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators' |
2 | import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' | 2 | import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' |
3 | import { fromEvent, Subscription } from 'rxjs' | 3 | import { fromEvent, Subscription } from 'rxjs' |
4 | 4 | ||
5 | @Directive({ | 5 | @Directive({ |
@@ -11,7 +11,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
11 | @Input() firstLoadedPage = 1 | 11 | @Input() firstLoadedPage = 1 |
12 | @Input() percentLimit = 70 | 12 | @Input() percentLimit = 70 |
13 | @Input() autoInit = false | 13 | @Input() autoInit = false |
14 | @Input() container = document.body | 14 | @Input() onItself = false |
15 | 15 | ||
16 | @Output() nearOfBottom = new EventEmitter<void>() | 16 | @Output() nearOfBottom = new EventEmitter<void>() |
17 | @Output() nearOfTop = new EventEmitter<void>() | 17 | @Output() nearOfTop = new EventEmitter<void>() |
@@ -24,8 +24,9 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
24 | private scrollUpSub: Subscription | 24 | private scrollUpSub: Subscription |
25 | private pageChangeSub: Subscription | 25 | private pageChangeSub: Subscription |
26 | private middleScreen: number | 26 | private middleScreen: number |
27 | private container: HTMLElement | ||
27 | 28 | ||
28 | constructor () { | 29 | constructor (private el: ElementRef) { |
29 | this.decimalLimit = this.percentLimit / 100 | 30 | this.decimalLimit = this.percentLimit / 100 |
30 | } | 31 | } |
31 | 32 | ||
@@ -40,16 +41,20 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
40 | } | 41 | } |
41 | 42 | ||
42 | initialize () { | 43 | initialize () { |
44 | if (this.onItself) { | ||
45 | this.container = this.el.nativeElement | ||
46 | } | ||
47 | |||
43 | this.middleScreen = window.innerHeight / 2 | 48 | this.middleScreen = window.innerHeight / 2 |
44 | 49 | ||
45 | // Emit the last value | 50 | // Emit the last value |
46 | const throttleOptions = { leading: true, trailing: true } | 51 | const throttleOptions = { leading: true, trailing: true } |
47 | 52 | ||
48 | const scrollObservable = fromEvent(window, 'scroll') | 53 | const scrollObservable = fromEvent(this.container || window, 'scroll') |
49 | .pipe( | 54 | .pipe( |
50 | startWith(null), | 55 | startWith(null), |
51 | throttleTime(200, undefined, throttleOptions), | 56 | throttleTime(200, undefined, throttleOptions), |
52 | map(() => ({ current: window.scrollY, maximumScroll: this.container.clientHeight - window.innerHeight })), | 57 | map(() => this.getScrollInfo()), |
53 | distinctUntilChanged((o1, o2) => o1.current === o2.current), | 58 | distinctUntilChanged((o1, o2) => o1.current === o2.current), |
54 | share() | 59 | share() |
55 | ) | 60 | ) |
@@ -102,4 +107,12 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
102 | // Offset page | 107 | // Offset page |
103 | return page + (this.firstLoadedPage - 1) | 108 | return page + (this.firstLoadedPage - 1) |
104 | } | 109 | } |
110 | |||
111 | private getScrollInfo () { | ||
112 | if (this.container) { | ||
113 | return { current: this.container.scrollTop, maximumScroll: this.container.scrollHeight } | ||
114 | } | ||
115 | |||
116 | return { current: window.scrollY, maximumScroll: document.body.clientHeight - window.innerHeight } | ||
117 | } | ||
105 | } | 118 | } |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 7f3d1cc2e..3df5b7b19 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | 9 | ||
10 | <div id="videojs-wrapper"></div> | 10 | <div id="videojs-wrapper"></div> |
11 | 11 | ||
12 | <div *ngIf="playlist && video" class="playlist"> | 12 | <div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()"> |
13 | <div class="playlist-info"> | 13 | <div class="playlist-info"> |
14 | <div class="playlist-display-name"> | 14 | <div class="playlist-display-name"> |
15 | {{ playlist.displayName }} | 15 | {{ playlist.displayName }} |
@@ -27,10 +27,10 @@ | |||
27 | </div> | 27 | </div> |
28 | </div> | 28 | </div> |
29 | 29 | ||
30 | <div *ngFor="let playlistVideo of playlistVideos" myInfiniteScroller [autoInit]="true" #elem [container]="elem" (nearOfBottom)="onPlaylistVideosNearOfBottom()"> | 30 | <div *ngFor="let playlistVideo of playlistVideos"> |
31 | <my-video-playlist-element-miniature | 31 | <my-video-playlist-element-miniature |
32 | [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" | 32 | [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)" |
33 | [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" | 33 | [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" [position]="playlistVideo.playlistElement.position" |
34 | ></my-video-playlist-element-miniature> | 34 | ></my-video-playlist-element-miniature> |
35 | </div> | 35 | </div> |
36 | </div> | 36 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index e1cb249ef..281b9240b 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -43,11 +43,12 @@ $other-videos-width: 260px; | |||
43 | .playlist { | 43 | .playlist { |
44 | width: 400px; | 44 | width: 400px; |
45 | height: 66vh; | 45 | height: 66vh; |
46 | background-color: #e4e4e4; | 46 | background-color: var(--mainBackgroundColor); |
47 | overflow-y: auto; | 47 | overflow-y: auto; |
48 | 48 | ||
49 | .playlist-info { | 49 | .playlist-info { |
50 | padding: 5px 30px; | 50 | padding: 5px 30px; |
51 | background-color: #e4e4e4; | ||
51 | 52 | ||
52 | .playlist-display-name { | 53 | .playlist-display-name { |
53 | font-size: 18px; | 54 | font-size: 18px; |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index ddd0f1766..adb728aba 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -58,7 +58,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
58 | playlistVideos: Video[] = [] | 58 | playlistVideos: Video[] = [] |
59 | playlistPagination: ComponentPagination = { | 59 | playlistPagination: ComponentPagination = { |
60 | currentPage: 1, | 60 | currentPage: 1, |
61 | itemsPerPage: 10, | 61 | itemsPerPage: 30, |
62 | totalItems: null | 62 | totalItems: null |
63 | } | 63 | } |
64 | noPlaylistVideos = false | 64 | noPlaylistVideos = false |
@@ -401,7 +401,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
401 | } | 401 | } |
402 | 402 | ||
403 | private loadPlaylistElements (redirectToFirst = false) { | 403 | private loadPlaylistElements (redirectToFirst = false) { |
404 | this.videoService.getPlaylistVideos(this.playlist.id, this.playlistPagination) | 404 | this.videoService.getPlaylistVideos(this.playlist.uuid, this.playlistPagination) |
405 | .subscribe(({ totalVideos, videos }) => { | 405 | .subscribe(({ totalVideos, videos }) => { |
406 | this.playlistVideos = this.playlistVideos.concat(videos) | 406 | this.playlistVideos = this.playlistVideos.concat(videos) |
407 | this.playlistPagination.totalItems = totalVideos | 407 | this.playlistPagination.totalItems = totalVideos |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 478737a43..28b466c01 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -104,7 +104,7 @@ label { | |||
104 | background-color: var(--submenuColor); | 104 | background-color: var(--submenuColor); |
105 | width: 100%; | 105 | width: 100%; |
106 | height: 81px; | 106 | height: 81px; |
107 | margin-bottom: 30px; | 107 | margin-bottom: $sub-menu-margin-bottom; |
108 | display: flex; | 108 | display: flex; |
109 | align-items: center; | 109 | align-items: center; |
110 | padding-left: $not-expanded-horizontal-margins; | 110 | padding-left: $not-expanded-horizontal-margins; |
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index deabbf6d4..b8eb06f2c 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss | |||
@@ -54,6 +54,8 @@ $theater-bottom-space: 115px; | |||
54 | $input-background-color: $bg-color; | 54 | $input-background-color: $bg-color; |
55 | $input-placeholder-color: #898989; | 55 | $input-placeholder-color: #898989; |
56 | 56 | ||
57 | $sub-menu-margin-bottom: 30px; | ||
58 | |||
57 | /*** map theme ***/ | 59 | /*** map theme ***/ |
58 | 60 | ||
59 | // pass variables into a sass map, | 61 | // pass variables into a sass map, |