aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-21 16:18:59 +0200
committerChocobozzz <me@florianbigard.com>2018-08-27 09:41:54 +0200
commit22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd (patch)
tree93c53e0619f966bd9ff1bb698c411277a9447a41 /client/src/app
parent99492dbc0d87ef54d0dab7d8d44f8d0de5722bdd (diff)
downloadPeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.tar.gz
PeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.tar.zst
PeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.zip
Add local user subscriptions
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.scss2
-rw-r--r--client/src/app/+my-account/my-account-routing.module.ts10
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html23
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss49
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts30
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts4
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss15
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.scss6
-rw-r--r--client/src/app/+my-account/my-account.component.html6
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/+my-account/shared/actor-avatar-info.component.scss2
-rw-r--r--client/src/app/+video-channels/video-channels.component.html3
-rw-r--r--client/src/app/+video-channels/video-channels.component.scss15
-rw-r--r--client/src/app/menu/menu.component.html5
-rw-r--r--client/src/app/menu/menu.component.scss6
-rw-r--r--client/src/app/shared/shared.module.ts9
-rw-r--r--client/src/app/shared/user-subscription/index.ts2
-rw-r--r--client/src/app/shared/user-subscription/subscribe-button.component.html15
-rw-r--r--client/src/app/shared/user-subscription/subscribe-button.component.scss37
-rw-r--r--client/src/app/shared/user-subscription/subscribe-button.component.ts74
-rw-r--r--client/src/app/shared/user-subscription/user-subscription.service.ts66
-rw-r--r--client/src/app/shared/video-channel/video-channel.service.ts30
-rw-r--r--client/src/app/shared/video/abstract-video-list.html2
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts2
-rw-r--r--client/src/app/shared/video/video-details.model.ts12
-rw-r--r--client/src/app/shared/video/video-miniature.component.html8
-rw-r--r--client/src/app/shared/video/video-miniature.component.scss3
-rw-r--r--client/src/app/shared/video/video-miniature.component.ts35
-rw-r--r--client/src/app/shared/video/video.model.ts8
-rw-r--r--client/src/app/shared/video/video.service.ts18
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html9
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss17
-rw-r--r--client/src/app/videos/video-list/video-user-subscriptions.component.ts57
-rw-r--r--client/src/app/videos/videos-routing.module.ts15
-rw-r--r--client/src/app/videos/videos.module.ts4
35 files changed, 531 insertions, 72 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
index c9c7fa8eb..39c0840e4 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
@@ -2,7 +2,7 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.row { 4.row {
5 text-align: center; 5 justify-content: center;
6} 6}
7 7
8a.video-channel { 8a.video-channel {
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 6f0806e8a..c1c979151 100644
--- a/client/src/app/+my-account/my-account-routing.module.ts
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -9,6 +9,7 @@ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-vid
9import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' 9import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
10import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' 10import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
11import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' 11import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
12import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
12 13
13const myAccountRoutes: Routes = [ 14const myAccountRoutes: Routes = [
14 { 15 {
@@ -74,6 +75,15 @@ const myAccountRoutes: Routes = [
74 title: 'Account video imports' 75 title: 'Account video imports'
75 } 76 }
76 } 77 }
78 },
79 {
80 path: 'subscriptions',
81 component: MyAccountSubscriptionsComponent,
82 data: {
83 meta: {
84 title: 'Account subscriptions'
85 }
86 }
77 } 87 }
78 ] 88 ]
79 } 89 }
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html
new file mode 100644
index 000000000..4c68cd1a5
--- /dev/null
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html
@@ -0,0 +1,23 @@
1<div class="video-channels">
2 <div *ngFor="let videoChannel of videoChannels" class="video-channel">
3 <a [routerLink]="[ '/video-channels', videoChannel.name ]">
4 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
5 </a>
6
7 <div class="video-channel-info">
8 <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel">
9 <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
10 <div class="video-channel-name">{{ videoChannel.name }}</div>
11 </a>
12
13 <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
14
15 <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Go the owner account page" class="actor-owner">
16 <span i18n>Created by {{ videoChannel.ownerBy }}</span>
17 <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" />
18 </a>
19 </div>
20
21 <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button>
22 </div>
23</div>
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss
new file mode 100644
index 000000000..2fbfa335b
--- /dev/null
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.scss
@@ -0,0 +1,49 @@
1@import '_variables';
2@import '_mixins';
3
4.video-channel {
5 @include row-blocks;
6
7 img {
8 @include avatar(80px);
9
10 margin-right: 10px;
11 }
12
13 .video-channel-info {
14 flex-grow: 1;
15
16 a.video-channel-names {
17 @include disable-default-a-behaviour;
18
19 width: fit-content;
20 display: flex;
21 align-items: baseline;
22 color: #000;
23
24 .video-channel-display-name {
25 font-weight: $font-semibold;
26 font-size: 18px;
27 }
28
29 .video-channel-name {
30 font-size: 14px;
31 color: $grey-actor-name;
32 margin-left: 5px;
33 }
34 }
35 }
36
37 .actor-owner {
38 @include actor-owner;
39 }
40
41 my-subscribe-button {
42 /deep/ span[role=button] {
43 padding: 7px 12px;
44 font-size: 16px;
45 }
46 }
47}
48
49
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
new file mode 100644
index 000000000..1e94cf90b
--- /dev/null
+++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
@@ -0,0 +1,30 @@
1import { Component, OnInit } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { UserSubscriptionService } from '@app/shared/user-subscription'
6
7@Component({
8 selector: 'my-account-subscriptions',
9 templateUrl: './my-account-subscriptions.component.html',
10 styleUrls: [ './my-account-subscriptions.component.scss' ]
11})
12export class MyAccountSubscriptionsComponent implements OnInit {
13 videoChannels: VideoChannel[] = []
14
15 constructor (
16 private userSubscriptionService: UserSubscriptionService,
17 private notificationsService: NotificationsService,
18 private i18n: I18n
19 ) {}
20
21 ngOnInit () {
22 this.userSubscriptionService.listSubscriptions()
23 .subscribe(
24 res => { console.log(res); this.videoChannels = res.data },
25
26 error => this.notificationsService.error(this.i18n('Error'), error.message)
27 )
28 }
29
30}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
index e25037e24..56697030b 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
@@ -78,7 +78,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
78 support: body.support || null 78 support: body.support || null
79 } 79 }
80 80
81 this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe( 81 this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe(
82 () => { 82 () => {
83 this.authService.refreshUserInformation() 83 this.authService.refreshUserInformation()
84 this.notificationsService.success( 84 this.notificationsService.success(
@@ -93,7 +93,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
93 } 93 }
94 94
95 onAvatarChange (formData: FormData) { 95 onAvatarChange (formData: FormData) {
96 this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.uuid, formData) 96 this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData)
97 .subscribe( 97 .subscribe(
98 data => { 98 data => {
99 this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) 99 this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.'))
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
index f8fd2684e..5c892be01 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
@@ -12,11 +12,7 @@
12} 12}
13 13
14.video-channel { 14.video-channel {
15 display: flex; 15 @include row-blocks;
16 min-height: 130px;
17 padding-bottom: 20px;
18 margin-bottom: 20px;
19 border-bottom: 1px solid #C6C6C6;
20 16
21 img { 17 img {
22 @include avatar(80px); 18 @include avatar(80px);
@@ -42,7 +38,7 @@
42 38
43 .video-channel-name { 39 .video-channel-name {
44 font-size: 14px; 40 font-size: 14px;
45 color: #777272; 41 color: $grey-actor-name;
46 margin-left: 5px; 42 margin-left: 5px;
47 } 43 }
48 } 44 }
@@ -64,12 +60,9 @@
64 } 60 }
65 61
66 .video-channel { 62 .video-channel {
67 flex-direction: column;
68 height: auto;
69 text-align: center;
70
71 .video-channel-names { 63 .video-channel-names {
72 justify-content: center; 64 flex-direction: column;
65 align-items: center !important;
73 } 66 }
74 67
75 img { 68 img {
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 64a04fa20..cd805be73 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
@@ -42,11 +42,7 @@
42} 42}
43 43
44.video { 44.video {
45 display: flex; 45 @include row-blocks;
46 min-height: 130px;
47 padding-bottom: 20px;
48 margin-bottom: 20px;
49 border-bottom: 1px solid #C6C6C6;
50 46
51 &:first-child { 47 &:first-child {
52 margin-top: 47px; 48 margin-top: 47px;
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html
index ddb0570db..74742649c 100644
--- a/client/src/app/+my-account/my-account.component.html
+++ b/client/src/app/+my-account/my-account.component.html
@@ -2,11 +2,13 @@
2 <div class="sub-menu"> 2 <div class="sub-menu">
3 <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a> 3 <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
4 4
5 <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a> 5 <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My channels</a>
6 6
7 <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a> 7 <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
8 8
9 <a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My video imports</a> 9 <a i18n routerLink="/my-account/subscriptions" routerLinkActive="active" class="title-page">My subscriptions</a>
10
11 <a *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My imports</a>
10 </div> 12 </div>
11 13
12 <div class="margin-content"> 14 <div class="margin-content">
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 29b49e8d9..c93f38d4b 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -14,6 +14,7 @@ import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-accoun
14import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' 14import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
15import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' 15import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
16import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' 16import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
17import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
17 18
18@NgModule({ 19@NgModule({
19 imports: [ 20 imports: [
@@ -34,7 +35,8 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin
34 MyAccountVideoChannelUpdateComponent, 35 MyAccountVideoChannelUpdateComponent,
35 ActorAvatarInfoComponent, 36 ActorAvatarInfoComponent,
36 MyAccountVideoImportsComponent, 37 MyAccountVideoImportsComponent,
37 MyAccountDangerZoneComponent 38 MyAccountDangerZoneComponent,
39 MyAccountSubscriptionsComponent
38 ], 40 ],
39 41
40 exports: [ 42 exports: [
diff --git a/client/src/app/+my-account/shared/actor-avatar-info.component.scss b/client/src/app/+my-account/shared/actor-avatar-info.component.scss
index 36a792f82..0b0c83de5 100644
--- a/client/src/app/+my-account/shared/actor-avatar-info.component.scss
+++ b/client/src/app/+my-account/shared/actor-avatar-info.component.scss
@@ -25,7 +25,7 @@
25 position: relative; 25 position: relative;
26 top: 2px; 26 top: 2px;
27 font-size: 14px; 27 font-size: 14px;
28 color: #777272; 28 color: $grey-actor-name;
29 } 29 }
30 } 30 }
31 31
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index 5a69a82a0..1941a2eab 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -8,6 +8,8 @@
8 <div class="actor-names"> 8 <div class="actor-names">
9 <div class="actor-display-name">{{ videoChannel.displayName }}</div> 9 <div class="actor-display-name">{{ videoChannel.displayName }}</div>
10 <div class="actor-name">{{ videoChannel.nameWithHost }}</div> 10 <div class="actor-name">{{ videoChannel.nameWithHost }}</div>
11
12 <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button>
11 </div> 13 </div>
12 <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> 14 <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div>
13 15
@@ -20,7 +22,6 @@
20 22
21 <div class="links"> 23 <div class="links">
22 <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> 24 <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
23
24 <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> 25 <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
25 </div> 26 </div>
26 </div> 27 </div>
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss
index 909b65bc7..a63b1ec06 100644
--- a/client/src/app/+video-channels/video-channels.component.scss
+++ b/client/src/app/+video-channels/video-channels.component.scss
@@ -3,4 +3,19 @@
3 3
4.sub-menu { 4.sub-menu {
5 @include sub-menu-with-actor; 5 @include sub-menu-with-actor;
6
7 .actor, .actor-info {
8 width: 100%;
9 }
10
11 .actor-name {
12 flex-grow: 1;
13 }
14
15 my-subscribe-button {
16 /deep/ span[role=button] {
17 padding: 7px 12px;
18 font-size: 16px;
19 }
20 }
6} \ No newline at end of file 21} \ No newline at end of file
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 7edcdf501..bd03af9b3 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -42,6 +42,11 @@
42 <div class="panel-block"> 42 <div class="panel-block">
43 <div i18n class="block-title">Videos</div> 43 <div i18n class="block-title">Videos</div>
44 44
45 <a *ngIf="isLoggedIn" routerLink="/videos/subscriptions" routerLinkActive="active">
46 <span class="icon icon-videos-subscriptions"></span>
47 <ng-container i18n>Subscriptions</ng-container>
48 </a>
49
45 <a routerLink="/videos/trending" routerLinkActive="active"> 50 <a routerLink="/videos/trending" routerLinkActive="active">
46 <span class="icon icon-videos-trending"></span> 51 <span class="icon icon-videos-trending"></span>
47 <ng-container i18n>Trending</ng-container> 52 <ng-container i18n>Trending</ng-container>
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 39f1e9be0..606fea961 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -135,6 +135,12 @@ menu {
135 135
136 margin-right: 18px; 136 margin-right: 18px;
137 137
138 &.icon-videos-subscriptions {
139 position: relative;
140 top: -2px;
141 background-image: url('../../assets/images/menu/subscriptions.svg');
142 }
143
138 &.icon-videos-trending { 144 &.icon-videos-trending {
139 position: relative; 145 position: relative;
140 top: -2px; 146 top: -2px;
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 722415a06..9bc7ad88b 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -36,7 +36,8 @@ import {
36 ReactiveFileComponent, 36 ReactiveFileComponent,
37 ResetPasswordValidatorsService, 37 ResetPasswordValidatorsService,
38 UserValidatorsService, 38 UserValidatorsService,
39 VideoAbuseValidatorsService, VideoBlacklistValidatorsService, 39 VideoAbuseValidatorsService,
40 VideoBlacklistValidatorsService,
40 VideoChannelValidatorsService, 41 VideoChannelValidatorsService,
41 VideoCommentValidatorsService, 42 VideoCommentValidatorsService,
42 VideoValidatorsService 43 VideoValidatorsService
@@ -49,6 +50,7 @@ import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.c
49import { VideoImportService } from '@app/shared/video-import/video-import.service' 50import { VideoImportService } from '@app/shared/video-import/video-import.service'
50import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' 51import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
51import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' 52import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
53import { SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
52 54
53@NgModule({ 55@NgModule({
54 imports: [ 56 imports: [
@@ -83,7 +85,8 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
83 InfiniteScrollerDirective, 85 InfiniteScrollerDirective,
84 HelpComponent, 86 HelpComponent,
85 ReactiveFileComponent, 87 ReactiveFileComponent,
86 PeertubeCheckboxComponent 88 PeertubeCheckboxComponent,
89 SubscribeButtonComponent
87 ], 90 ],
88 91
89 exports: [ 92 exports: [
@@ -115,6 +118,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
115 HelpComponent, 118 HelpComponent,
116 ReactiveFileComponent, 119 ReactiveFileComponent,
117 PeertubeCheckboxComponent, 120 PeertubeCheckboxComponent,
121 SubscribeButtonComponent,
118 122
119 NumberFormatterPipe, 123 NumberFormatterPipe,
120 ObjectLengthPipe, 124 ObjectLengthPipe,
@@ -134,6 +138,7 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
134 VideoChannelService, 138 VideoChannelService,
135 VideoCaptionService, 139 VideoCaptionService,
136 VideoImportService, 140 VideoImportService,
141 UserSubscriptionService,
137 142
138 FormValidatorService, 143 FormValidatorService,
139 CustomConfigValidatorsService, 144 CustomConfigValidatorsService,
diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/user-subscription/index.ts
new file mode 100644
index 000000000..024b36a41
--- /dev/null
+++ b/client/src/app/shared/user-subscription/index.ts
@@ -0,0 +1,2 @@
1export * from './user-subscription.service'
2export * from './subscribe-button.component' \ No newline at end of file
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/user-subscription/subscribe-button.component.html
new file mode 100644
index 000000000..63b313662
--- /dev/null
+++ b/client/src/app/shared/user-subscription/subscribe-button.component.html
@@ -0,0 +1,15 @@
1<span i18n *ngIf="subscribed === false" class="subscribe-button" role="button" (click)="subscribe()">
2 <span>Subscribe</span>
3 <span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count">
4 {{ videoChannel.followersCount | myNumberFormatter }}
5 </span>
6</span>
7
8<span *ngIf="subscribed === true" class="unsubscribe-button" role="button" (click)="unsubscribe()">
9 <span class="subscribed" i18n>Subscribed</span>
10 <span class="unsubscribe" i18n>Unsubscribe</span>
11
12 <span *ngIf="displayFollowers && videoChannel.followersCount !== 0" class="followers-count">
13 {{ videoChannel.followersCount | myNumberFormatter }}
14 </span>
15</span>
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/user-subscription/subscribe-button.component.scss
new file mode 100644
index 000000000..9811fdc0c
--- /dev/null
+++ b/client/src/app/shared/user-subscription/subscribe-button.component.scss
@@ -0,0 +1,37 @@
1@import '_variables';
2@import '_mixins';
3
4.subscribe-button {
5 @include peertube-button;
6 @include orange-button;
7}
8
9.unsubscribe-button {
10 @include peertube-button;
11 @include grey-button
12}
13
14.subscribe-button,
15.unsubscribe-button {
16 padding: 3px 7px;
17}
18
19.unsubscribe-button {
20 .subscribed {
21 display: inline;
22 }
23
24 .unsubscribe {
25 display: none;
26 }
27
28 &:hover {
29 .subscribed {
30 display: none;
31 }
32
33 .unsubscribe {
34 display: inline;
35 }
36 }
37} \ No newline at end of file
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/user-subscription/subscribe-button.component.ts
new file mode 100644
index 000000000..46d6dbaf7
--- /dev/null
+++ b/client/src/app/shared/user-subscription/subscribe-button.component.ts
@@ -0,0 +1,74 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { AuthService } from '@app/core'
3import { RestExtractor } from '@app/shared/rest'
4import { RedirectService } from '@app/core/routing/redirect.service'
5import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
6import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
7import { NotificationsService } from 'angular2-notifications'
8import { I18n } from '@ngx-translate/i18n-polyfill'
9
10@Component({
11 selector: 'my-subscribe-button',
12 templateUrl: './subscribe-button.component.html',
13 styleUrls: [ './subscribe-button.component.scss' ]
14})
15export class SubscribeButtonComponent implements OnInit {
16 @Input() videoChannel: VideoChannel
17 @Input() displayFollowers = false
18
19 subscribed: boolean
20
21 constructor (
22 private authService: AuthService,
23 private restExtractor: RestExtractor,
24 private redirectService: RedirectService,
25 private notificationsService: NotificationsService,
26 private userSubscriptionService: UserSubscriptionService,
27 private i18n: I18n
28 ) { }
29
30 get uri () {
31 return this.videoChannel.name + '@' + this.videoChannel.host
32 }
33
34 ngOnInit () {
35 this.userSubscriptionService.isSubscriptionExists(this.uri)
36 .subscribe(
37 exists => this.subscribed = exists,
38
39 err => this.notificationsService.error(this.i18n('Error'), err.message)
40 )
41 }
42
43 subscribe () {
44 this.userSubscriptionService.addSubscription(this.uri)
45 .subscribe(
46 () => {
47 this.subscribed = true
48
49 this.notificationsService.success(
50 this.i18n('Subscribed'),
51 this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
52 )
53 },
54
55 err => this.notificationsService.error(this.i18n('Error'), err.message)
56 )
57 }
58
59 unsubscribe () {
60 this.userSubscriptionService.deleteSubscription(this.uri)
61 .subscribe(
62 () => {
63 this.subscribed = false
64
65 this.notificationsService.success(
66 this.i18n('Unsubscribed'),
67 this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
68 )
69 },
70
71 err => this.notificationsService.error(this.i18n('Error'), err.message)
72 )
73 }
74}
diff --git a/client/src/app/shared/user-subscription/user-subscription.service.ts b/client/src/app/shared/user-subscription/user-subscription.service.ts
new file mode 100644
index 000000000..3103706d1
--- /dev/null
+++ b/client/src/app/shared/user-subscription/user-subscription.service.ts
@@ -0,0 +1,66 @@
1import { catchError, map } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { ResultList } from '../../../../../shared'
5import { environment } from '../../../environments/environment'
6import { RestExtractor } from '../rest'
7import { Observable, of } from 'rxjs'
8import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
9import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
10import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos'
11
12@Injectable()
13export class UserSubscriptionService {
14 static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions'
15
16 constructor (
17 private authHttp: HttpClient,
18 private restExtractor: RestExtractor
19 ) {
20 }
21
22 deleteSubscription (nameWithHost: string) {
23 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost
24
25 return this.authHttp.delete(url)
26 .pipe(
27 map(this.restExtractor.extractDataBool),
28 catchError(err => this.restExtractor.handleError(err))
29 )
30 }
31
32 addSubscription (nameWithHost: string) {
33 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
34
35 const body = { uri: nameWithHost }
36 return this.authHttp.post(url, body)
37 .pipe(
38 map(this.restExtractor.extractDataBool),
39 catchError(err => this.restExtractor.handleError(err))
40 )
41 }
42
43 listSubscriptions (): Observable<ResultList<VideoChannel>> {
44 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL
45
46 return this.authHttp.get<ResultList<VideoChannelServer>>(url)
47 .pipe(
48 map(res => VideoChannelService.extractVideoChannels(res)),
49 catchError(err => this.restExtractor.handleError(err))
50 )
51 }
52
53 isSubscriptionExists (nameWithHost: string): Observable<boolean> {
54 const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost
55
56 return this.authHttp.get(url)
57 .pipe(
58 map(this.restExtractor.extractDataBool),
59 catchError(err => {
60 if (err.status === 404) return of(false)
61
62 return this.restExtractor.handleError(err)
63 })
64 )
65 }
66}
diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts
index 510dc9c3d..46b121790 100644
--- a/client/src/app/shared/video-channel/video-channel.service.ts
+++ b/client/src/app/shared/video-channel/video-channel.service.ts
@@ -22,6 +22,16 @@ export class VideoChannelService {
22 private restExtractor: RestExtractor 22 private restExtractor: RestExtractor
23 ) {} 23 ) {}
24 24
25 static extractVideoChannels (result: ResultList<VideoChannelServer>) {
26 const videoChannels: VideoChannel[] = []
27
28 for (const videoChannelJSON of result.data) {
29 videoChannels.push(new VideoChannel(videoChannelJSON))
30 }
31
32 return { data: videoChannels, total: result.total }
33 }
34
25 getVideoChannel (videoChannelName: string) { 35 getVideoChannel (videoChannelName: string) {
26 return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) 36 return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName)
27 .pipe( 37 .pipe(
@@ -34,7 +44,7 @@ export class VideoChannelService {
34 listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { 44 listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> {
35 return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') 45 return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels')
36 .pipe( 46 .pipe(
37 map(res => this.extractVideoChannels(res)), 47 map(res => VideoChannelService.extractVideoChannels(res)),
38 catchError(err => this.restExtractor.handleError(err)) 48 catchError(err => this.restExtractor.handleError(err))
39 ) 49 )
40 } 50 }
@@ -47,16 +57,16 @@ export class VideoChannelService {
47 ) 57 )
48 } 58 }
49 59
50 updateVideoChannel (videoChannelUUID: string, videoChannel: VideoChannelUpdate) { 60 updateVideoChannel (videoChannelName: string, videoChannel: VideoChannelUpdate) {
51 return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID, videoChannel) 61 return this.authHttp.put(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName, videoChannel)
52 .pipe( 62 .pipe(
53 map(this.restExtractor.extractDataBool), 63 map(this.restExtractor.extractDataBool),
54 catchError(err => this.restExtractor.handleError(err)) 64 catchError(err => this.restExtractor.handleError(err))
55 ) 65 )
56 } 66 }
57 67
58 changeVideoChannelAvatar (videoChannelUUID: string, avatarForm: FormData) { 68 changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) {
59 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID + '/avatar/pick' 69 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick'
60 70
61 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) 71 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm)
62 .pipe(catchError(err => this.restExtractor.handleError(err))) 72 .pipe(catchError(err => this.restExtractor.handleError(err)))
@@ -69,14 +79,4 @@ export class VideoChannelService {
69 catchError(err => this.restExtractor.handleError(err)) 79 catchError(err => this.restExtractor.handleError(err))
70 ) 80 )
71 } 81 }
72
73 private extractVideoChannels (result: ResultList<VideoChannelServer>) {
74 const videoChannels: VideoChannel[] = []
75
76 for (const videoChannelJSON of result.data) {
77 videoChannels.push(new VideoChannel(videoChannelJSON))
78 }
79
80 return { data: videoChannels, total: result.total }
81 }
82} 82}
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index e8ded6ab8..d4b00c07c 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -14,7 +14,7 @@
14 <div *ngFor="let videos of videoPages" class="videos-page"> 14 <div *ngFor="let videos of videoPages" class="videos-page">
15 <my-video-miniature 15 <my-video-miniature
16 class="ng-animate" 16 class="ng-animate"
17 *ngFor="let video of videos" [video]="video" [user]="user" 17 *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
18 > 18 >
19 </my-video-miniature> 19 </my-video-miniature>
20 </div> 20 </div>
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 59d3c1ebe..b8fd7f8eb 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -11,6 +11,7 @@ import { VideoSortField } from './sort-field.type'
11import { Video } from './video.model' 11import { Video } from './video.model'
12import { I18n } from '@ngx-translate/i18n-polyfill' 12import { I18n } from '@ngx-translate/i18n-polyfill'
13import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
14import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
14 15
15export abstract class AbstractVideoList implements OnInit, OnDestroy { 16export abstract class AbstractVideoList implements OnInit, OnDestroy {
16 private static LINES_PER_PAGE = 4 17 private static LINES_PER_PAGE = 4
@@ -34,6 +35,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
34 videoWidth: number 35 videoWidth: number
35 videoHeight: number 36 videoHeight: number
36 videoPages: Video[][] = [] 37 videoPages: Video[][] = []
38 ownerDisplayType: OwnerDisplayType = 'account'
37 39
38 protected baseVideoWidth = 215 40 protected baseVideoWidth = 215
39 protected baseVideoHeight = 230 41 protected baseVideoHeight = 230
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index d346f985c..fa4ca7f93 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,14 +1,8 @@
1import { 1import { UserRight, VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared'
2 UserRight,
3 VideoChannel,
4 VideoConstant,
5 VideoDetails as VideoDetailsServerModel,
6 VideoFile,
7 VideoState
8} from '../../../../../shared'
9import { AuthUser } from '../../core' 2import { AuthUser } from '../../core'
10import { Video } from '../../shared/video/video.model' 3import { Video } from '../../shared/video/video.model'
11import { Account } from '@app/shared/account/account.model' 4import { Account } from '@app/shared/account/account.model'
5import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
12 6
13export class VideoDetails extends Video implements VideoDetailsServerModel { 7export class VideoDetails extends Video implements VideoDetailsServerModel {
14 descriptionPath: string 8 descriptionPath: string
@@ -30,7 +24,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
30 24
31 this.descriptionPath = hash.descriptionPath 25 this.descriptionPath = hash.descriptionPath
32 this.files = hash.files 26 this.files = hash.files
33 this.channel = hash.channel 27 this.channel = new VideoChannel(hash.channel)
34 this.account = new Account(hash.account) 28 this.account = new Account(hash.account)
35 this.tags = hash.tags 29 this.tags = hash.tags
36 this.support = hash.support 30 this.support = hash.support
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
index 3010e5ccc..de84bccf9 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -10,6 +10,12 @@
10 </a> 10 </a>
11 11
12 <span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> 12 <span i18n class="video-miniature-created-at-views">{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
13 <a class="video-miniature-account" [routerLink]="[ '/accounts', video.by ]">{{ video.by }}</a> 13
14 <a *ngIf="displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
15 {{ video.byAccount }}
16 </a>
17 <a *ngIf="displayOwnerVideoChannel()" class="video-miniature-channel" [routerLink]="[ '/video-channels', video.byVideoChannel ]">
18 {{ video.byVideoChannel }}
19 </a>
14 </div> 20 </div>
15</div> 21</div>
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/video/video-miniature.component.scss
index 588eea3a7..6883650f4 100644
--- a/client/src/app/shared/video/video-miniature.component.scss
+++ b/client/src/app/shared/video/video-miniature.component.scss
@@ -38,7 +38,8 @@
38 font-size: 13px; 38 font-size: 13px;
39 } 39 }
40 40
41 .video-miniature-account { 41 .video-miniature-account,
42 .video-miniature-channel {
42 @include disable-default-a-behaviour; 43 @include disable-default-a-behaviour;
43 44
44 display: block; 45 display: block;
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
index d3f6dc1f6..07193ebd5 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -1,20 +1,51 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { User } from '../users' 2import { User } from '../users'
3import { Video } from './video.model' 3import { Video } from './video.model'
4import { ServerService } from '@app/core' 4import { ServerService } from '@app/core'
5 5
6export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
7
6@Component({ 8@Component({
7 selector: 'my-video-miniature', 9 selector: 'my-video-miniature',
8 styleUrls: [ './video-miniature.component.scss' ], 10 styleUrls: [ './video-miniature.component.scss' ],
9 templateUrl: './video-miniature.component.html' 11 templateUrl: './video-miniature.component.html'
10}) 12})
11export class VideoMiniatureComponent { 13export class VideoMiniatureComponent implements OnInit {
12 @Input() user: User 14 @Input() user: User
13 @Input() video: Video 15 @Input() video: Video
16 @Input() ownerDisplayType: OwnerDisplayType = 'account'
17
18 private ownerDisplayTypeChosen: 'account' | 'videoChannel'
14 19
15 constructor (private serverService: ServerService) { } 20 constructor (private serverService: ServerService) { }
16 21
22 ngOnInit () {
23 if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
24 this.ownerDisplayTypeChosen = this.ownerDisplayType
25 return
26 }
27
28 // If the video channel name an UUID (not really displayable, we changed this behaviour in v1.0.0-beta.12)
29 // -> Use the account name
30 if (
31 this.video.channel.name === `${this.video.account.name}_channel` ||
32 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}$/)
33 ) {
34 this.ownerDisplayTypeChosen = 'account'
35 } else {
36 this.ownerDisplayTypeChosen = 'videoChannel'
37 }
38 }
39
17 isVideoBlur () { 40 isVideoBlur () {
18 return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) 41 return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
19 } 42 }
43
44 displayOwnerAccount () {
45 return this.ownerDisplayTypeChosen === 'account'
46 }
47
48 displayOwnerVideoChannel () {
49 return this.ownerDisplayTypeChosen === 'videoChannel'
50 }
20} 51}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index df8253301..d80c10459 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -8,9 +8,12 @@ import { Actor } from '@app/shared/actor/actor.model'
8import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' 8import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
9 9
10export class Video implements VideoServerModel { 10export class Video implements VideoServerModel {
11 by: string 11 byVideoChannel: string
12 byAccount: string
13
12 accountAvatarUrl: string 14 accountAvatarUrl: string
13 videoChannelAvatarUrl: string 15 videoChannelAvatarUrl: string
16
14 createdAt: Date 17 createdAt: Date
15 updatedAt: Date 18 updatedAt: Date
16 publishedAt: Date 19 publishedAt: Date
@@ -110,7 +113,8 @@ export class Video implements VideoServerModel {
110 this.account = hash.account 113 this.account = hash.account
111 this.channel = hash.channel 114 this.channel = hash.channel
112 115
113 this.by = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) 116 this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host)
117 this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host)
114 this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) 118 this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account)
115 this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel) 119 this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.channel)
116 120
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index e44f1ee65..1a934c8e2 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -27,6 +27,7 @@ import { Account } from '@app/shared/account/account.model'
27import { AccountService } from '@app/shared/account/account.service' 27import { AccountService } from '@app/shared/account/account.service'
28import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 28import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
29import { ServerService } from '@app/core' 29import { ServerService } from '@app/core'
30import { UserSubscriptionService } from '@app/shared/user-subscription'
30 31
31@Injectable() 32@Injectable()
32export class VideoService { 33export class VideoService {
@@ -157,6 +158,23 @@ export class VideoService {
157 ) 158 )
158 } 159 }
159 160
161 getUserSubscriptionVideos (
162 videoPagination: ComponentPagination,
163 sort: VideoSortField
164 ): Observable<{ videos: Video[], totalVideos: number }> {
165 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
166
167 let params = new HttpParams()
168 params = this.restService.addRestGetParams(params, pagination, sort)
169
170 return this.authHttp
171 .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
172 .pipe(
173 switchMap(res => this.extractVideos(res)),
174 catchError(err => this.restExtractor.handleError(err))
175 )
176 }
177
160 getVideos ( 178 getVideos (
161 videoPagination: ComponentPagination, 179 videoPagination: ComponentPagination,
162 sort: VideoSortField, 180 sort: VideoSortField,
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 c275258ef..8a49e3566 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -42,16 +42,17 @@
42 42
43 <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> 43 <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" />
44 </a> 44 </a>
45 <!-- Here will be the subscribe button --> 45
46 <my-subscribe-button [videoChannel]="video.channel"></my-subscribe-button>
46 </div> 47 </div>
47 48
48 <div class="video-info-by"> 49 <div class="video-info-by">
49 <a [routerLink]="[ '/accounts', video.by ]" i18n-title title="Go to the account page"> 50 <a [routerLink]="[ '/accounts', video.byAccount ]" i18n-title title="Go to the account page">
50 <span i18n>By {{ video.by }}</span> 51 <span i18n>By {{ video.byAccount }}</span>
51 <img [src]="video.accountAvatarUrl" alt="Account avatar" /> 52 <img [src]="video.accountAvatarUrl" alt="Account avatar" />
52 </a> 53 </a>
53 54
54 <my-help helpType="custom" i18n-customHtml customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.name}}@{{video.account.host}}</strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>."></my-help> 55 <my-help helpType="custom" i18n-customHtml customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.name}}@{{video.account.host}}</strong> and subscribe there."></my-help>
55 </div> 56 </div>
56 </div> 57 </div>
57 58
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 1354de32e..5bf2f485a 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -125,6 +125,14 @@
125 margin: -2px 2px 0 5px; 125 margin: -2px 2px 0 5px;
126 } 126 }
127 } 127 }
128
129 my-subscribe-button {
130 /deep/ span[role=button] {
131 font-size: 13px !important;
132 }
133
134 margin-left: 5px;
135 }
128 } 136 }
129 137
130 .video-info-by { 138 .video-info-by {
@@ -369,7 +377,10 @@
369 377
370 .video-miniature-information { 378 .video-miniature-information {
371 flex-grow: 1; 379 flex-grow: 1;
372 margin-left: 10px; 380 }
381
382 .video-thumbnail {
383 margin-right: 10px
373 } 384 }
374 } 385 }
375 } 386 }
@@ -502,10 +513,6 @@
502 .other-videos { 513 .other-videos {
503 /deep/ .video-miniature { 514 /deep/ .video-miniature {
504 flex-direction: column; 515 flex-direction: column;
505
506 .video-miniature-information {
507 margin-left: 0 !important;
508 }
509 } 516 }
510 } 517 }
511 518
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
new file mode 100644
index 000000000..6e8959c54
--- /dev/null
+++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
@@ -0,0 +1,57 @@
1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { immutableAssign } from '@app/shared/misc/utils'
4import { Location } from '@angular/common'
5import { NotificationsService } from 'angular2-notifications'
6import { AuthService } from '../../core/auth'
7import { AbstractVideoList } from '../../shared/video/abstract-video-list'
8import { VideoSortField } from '../../shared/video/sort-field.type'
9import { VideoService } from '../../shared/video/video.service'
10import { I18n } from '@ngx-translate/i18n-polyfill'
11import { ScreenService } from '@app/shared/misc/screen.service'
12import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
13
14@Component({
15 selector: 'my-videos-user-subscriptions',
16 styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
17 templateUrl: '../../shared/video/abstract-video-list.html'
18})
19export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy {
20 titlePage: string
21 currentRoute = '/videos/subscriptions'
22 sort = '-publishedAt' as VideoSortField
23 ownerDisplayType: OwnerDisplayType = 'auto'
24
25 constructor (
26 protected router: Router,
27 protected route: ActivatedRoute,
28 protected notificationsService: NotificationsService,
29 protected authService: AuthService,
30 protected location: Location,
31 protected i18n: I18n,
32 protected screenService: ScreenService,
33 private videoService: VideoService
34 ) {
35 super()
36
37 this.titlePage = i18n('Videos from your subscriptions')
38 }
39
40 ngOnInit () {
41 super.ngOnInit()
42 }
43
44 ngOnDestroy () {
45 super.ngOnDestroy()
46 }
47
48 getVideosObservable (page: number) {
49 const newPagination = immutableAssign(this.pagination, { currentPage: page })
50
51 return this.videoService.getUserSubscriptionVideos(newPagination, this.sort)
52 }
53
54 generateSyndicationList () {
55 // not implemented yet
56 }
57}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 538a43c6d..18ed52570 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -5,6 +5,7 @@ import { MetaGuard } from '@ngx-meta/core'
5import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' 5import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
6import { VideoTrendingComponent } from './video-list/video-trending.component' 6import { VideoTrendingComponent } from './video-list/video-trending.component'
7import { VideosComponent } from './videos.component' 7import { VideosComponent } from './videos.component'
8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
8 9
9const videosRoutes: Routes = [ 10const videosRoutes: Routes = [
10 { 11 {
@@ -13,11 +14,6 @@ const videosRoutes: Routes = [
13 canActivateChild: [ MetaGuard ], 14 canActivateChild: [ MetaGuard ],
14 children: [ 15 children: [
15 { 16 {
16 path: 'list',
17 pathMatch: 'full',
18 redirectTo: 'recently-added'
19 },
20 {
21 path: 'trending', 17 path: 'trending',
22 component: VideoTrendingComponent, 18 component: VideoTrendingComponent,
23 data: { 19 data: {
@@ -36,6 +32,15 @@ const videosRoutes: Routes = [
36 } 32 }
37 }, 33 },
38 { 34 {
35 path: 'subscriptions',
36 component: VideoUserSubscriptionsComponent,
37 data: {
38 meta: {
39 title: 'Subscriptions'
40 }
41 }
42 },
43 {
39 path: 'local', 44 path: 'local',
40 component: VideoLocalComponent, 45 component: VideoLocalComponent,
41 data: { 46 data: {
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index c38257e08..3c3877273 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -5,6 +5,7 @@ import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.c
5import { VideoTrendingComponent } from './video-list/video-trending.component' 5import { VideoTrendingComponent } from './video-list/video-trending.component'
6import { VideosRoutingModule } from './videos-routing.module' 6import { VideosRoutingModule } from './videos-routing.module'
7import { VideosComponent } from './videos.component' 7import { VideosComponent } from './videos.component'
8import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component'
8 9
9@NgModule({ 10@NgModule({
10 imports: [ 11 imports: [
@@ -17,7 +18,8 @@ import { VideosComponent } from './videos.component'
17 18
18 VideoTrendingComponent, 19 VideoTrendingComponent,
19 VideoRecentlyAddedComponent, 20 VideoRecentlyAddedComponent,
20 VideoLocalComponent 21 VideoLocalComponent,
22 VideoUserSubscriptionsComponent
21 ], 23 ],
22 24
23 exports: [ 25 exports: [