diff options
author | Chocobozzz <me@florianbigard.com> | 2018-08-21 16:18:59 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-27 09:41:54 +0200 |
commit | 22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd (patch) | |
tree | 93c53e0619f966bd9ff1bb698c411277a9447a41 /client/src/app | |
parent | 99492dbc0d87ef54d0dab7d8d44f8d0de5722bdd (diff) | |
download | PeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.tar.gz PeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.tar.zst PeerTube-22a16e36f6526887ed8f5e5d3c9f9e5da0b4a8cd.zip |
Add local user subscriptions
Diffstat (limited to 'client/src/app')
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 | ||
8 | a.video-channel { | 8 | a.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 | |||
9 | import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' | 9 | import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component' |
10 | import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' | 10 | import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component' |
11 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' | 11 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' |
12 | import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component' | ||
12 | 13 | ||
13 | const myAccountRoutes: Routes = [ | 14 | const 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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { NotificationsService } from 'angular2-notifications' | ||
3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { 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 | }) | ||
12 | export 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 | |||
14 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' | 14 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' |
15 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' | 15 | import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component' |
16 | import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' | 16 | import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone' |
17 | import { 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 | |||
49 | import { VideoImportService } from '@app/shared/video-import/video-import.service' | 50 | import { VideoImportService } from '@app/shared/video-import/video-import.service' |
50 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' | 51 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' |
51 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 52 | import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
53 | import { 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 @@ | |||
1 | export * from './user-subscription.service' | ||
2 | export * 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 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { AuthService } from '@app/core' | ||
3 | import { RestExtractor } from '@app/shared/rest' | ||
4 | import { RedirectService } from '@app/core/routing/redirect.service' | ||
5 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | ||
6 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
7 | import { NotificationsService } from 'angular2-notifications' | ||
8 | import { 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 | }) | ||
15 | export 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 @@ | |||
1 | import { catchError, map } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { ResultList } from '../../../../../shared' | ||
5 | import { environment } from '../../../environments/environment' | ||
6 | import { RestExtractor } from '../rest' | ||
7 | import { Observable, of } from 'rxjs' | ||
8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
10 | import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos' | ||
11 | |||
12 | @Injectable() | ||
13 | export 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' | |||
11 | import { Video } from './video.model' | 11 | import { Video } from './video.model' |
12 | import { I18n } from '@ngx-translate/i18n-polyfill' | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
13 | import { ScreenService } from '@app/shared/misc/screen.service' | 13 | import { ScreenService } from '@app/shared/misc/screen.service' |
14 | import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' | ||
14 | 15 | ||
15 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | 16 | export 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 @@ | |||
1 | import { | 1 | import { 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' | ||
9 | import { AuthUser } from '../../core' | 2 | import { AuthUser } from '../../core' |
10 | import { Video } from '../../shared/video/video.model' | 3 | import { Video } from '../../shared/video/video.model' |
11 | import { Account } from '@app/shared/account/account.model' | 4 | import { Account } from '@app/shared/account/account.model' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
12 | 6 | ||
13 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 7 | export 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 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { User } from '../users' | 2 | import { User } from '../users' |
3 | import { Video } from './video.model' | 3 | import { Video } from './video.model' |
4 | import { ServerService } from '@app/core' | 4 | import { ServerService } from '@app/core' |
5 | 5 | ||
6 | export 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 | }) |
11 | export class VideoMiniatureComponent { | 13 | export 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' | |||
8 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' | 8 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' |
9 | 9 | ||
10 | export class Video implements VideoServerModel { | 10 | export 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' | |||
27 | import { AccountService } from '@app/shared/account/account.service' | 27 | import { AccountService } from '@app/shared/account/account.service' |
28 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 28 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
29 | import { ServerService } from '@app/core' | 29 | import { ServerService } from '@app/core' |
30 | import { UserSubscriptionService } from '@app/shared/user-subscription' | ||
30 | 31 | ||
31 | @Injectable() | 32 | @Injectable() |
32 | export class VideoService { | 33 | export 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 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { immutableAssign } from '@app/shared/misc/utils' | ||
4 | import { Location } from '@angular/common' | ||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { AuthService } from '../../core/auth' | ||
7 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
8 | import { VideoSortField } from '../../shared/video/sort-field.type' | ||
9 | import { VideoService } from '../../shared/video/video.service' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
11 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
12 | import { 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 | }) | ||
19 | export 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' | |||
5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' | 5 | import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' |
6 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 6 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { VideoUserSubscriptionsComponent } from '@app/videos/video-list/video-user-subscriptions.component' | ||
8 | 9 | ||
9 | const videosRoutes: Routes = [ | 10 | const 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 | |||
5 | import { VideoTrendingComponent } from './video-list/video-trending.component' | 5 | import { VideoTrendingComponent } from './video-list/video-trending.component' |
6 | import { VideosRoutingModule } from './videos-routing.module' | 6 | import { VideosRoutingModule } from './videos-routing.module' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | import { 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: [ |