diff options
author | Chocobozzz <me@florianbigard.com> | 2021-10-19 09:44:43 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-10-20 09:25:44 +0200 |
commit | 4beda9e12adc7b1f3b178cecd6863ebf3cf431f1 (patch) | |
tree | 6244a10b286d66c6dcd7799aee630670d0493781 /client/src/app/+my-library/my-follows | |
parent | 9593a78ae1368a9ad8bb11044fce6fde2892701a (diff) | |
download | PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.tar.gz PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.tar.zst PeerTube-4beda9e12adc7b1f3b178cecd6863ebf3cf431f1.zip |
Add ability to view my followers
Diffstat (limited to 'client/src/app/+my-library/my-follows')
6 files changed, 242 insertions, 0 deletions
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.html b/client/src/app/+my-library/my-follows/my-followers.component.html new file mode 100644 index 000000000..d2b2dccb6 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.html | |||
@@ -0,0 +1,31 @@ | |||
1 | <h1> | ||
2 | <span> | ||
3 | <my-global-icon iconName="follower" aria-hidden="true"></my-global-icon> | ||
4 | <ng-container i18n>My followers</ng-container> | ||
5 | <span class="badge badge-secondary"> {{ pagination.totalItems }}</span> | ||
6 | </span> | ||
7 | </h1> | ||
8 | |||
9 | <div class="followers-header"> | ||
10 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | ||
11 | </div> | ||
12 | |||
13 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No follower found.</div> | ||
14 | |||
15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> | ||
16 | <div *ngFor="let follow of follows" class="actor"> | ||
17 | <my-actor-avatar [account]="follow.follower" [href]="follow.follower.url"></my-actor-avatar> | ||
18 | |||
19 | <div class="actor-info"> | ||
20 | <a [href]="follow.follower.url" class="actor-names" rel="noopener noreferrer" target="_blank" i18n-title title="Follower page"> | ||
21 | <div class="actor-display-name">{{ follow.follower.name + '@' + follow.follower.host }}</div> | ||
22 | <span class="glyphicon glyphicon-new-window"></span> | ||
23 | </a> | ||
24 | |||
25 | <div class="text-muted"> | ||
26 | <ng-container *ngIf="isFollowingAccount(follow)" i18n>Is following all your channels</ng-container> | ||
27 | <ng-container *ngIf="!isFollowingAccount(follow)" i18n>Is following your channel {{ follow.following.name }}</ng-container> | ||
28 | </div> | ||
29 | </div> | ||
30 | </div> | ||
31 | </div> | ||
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.scss b/client/src/app/+my-library/my-follows/my-followers.component.scss new file mode 100644 index 000000000..15b51c419 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.scss | |||
@@ -0,0 +1,26 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | @use '_actor' as *; | ||
4 | |||
5 | .followers-header { | ||
6 | margin-bottom: 30px; | ||
7 | display: flex; | ||
8 | } | ||
9 | |||
10 | input[type=text] { | ||
11 | @include peertube-input-text(300px); | ||
12 | } | ||
13 | |||
14 | .actor { | ||
15 | @include actor-row($avatar-size: 40px, $min-height: auto, $separator: true); | ||
16 | |||
17 | .actor-display-name { | ||
18 | font-size: 16px; | ||
19 | |||
20 | + .glyphicon { | ||
21 | @include margin-left(5px); | ||
22 | |||
23 | font-size: 12px; | ||
24 | } | ||
25 | } | ||
26 | } | ||
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts new file mode 100644 index 000000000..a7bbe6d99 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-followers.component.ts | |||
@@ -0,0 +1,76 @@ | |||
1 | import { Subject } from 'rxjs' | ||
2 | import { Component, OnInit } from '@angular/core' | ||
3 | import { ActivatedRoute } from '@angular/router' | ||
4 | import { AuthService, ComponentPagination, Notifier } from '@app/core' | ||
5 | import { UserSubscriptionService } from '@app/shared/shared-user-subscription' | ||
6 | import { ActorFollow } from '@shared/models' | ||
7 | |||
8 | @Component({ | ||
9 | templateUrl: './my-followers.component.html', | ||
10 | styleUrls: [ './my-followers.component.scss' ] | ||
11 | }) | ||
12 | export class MyFollowersComponent implements OnInit { | ||
13 | follows: ActorFollow[] = [] | ||
14 | |||
15 | pagination: ComponentPagination = { | ||
16 | currentPage: 1, | ||
17 | itemsPerPage: 10, | ||
18 | totalItems: null | ||
19 | } | ||
20 | |||
21 | onDataSubject = new Subject<any[]>() | ||
22 | search: string | ||
23 | |||
24 | constructor ( | ||
25 | private route: ActivatedRoute, | ||
26 | private auth: AuthService, | ||
27 | private userSubscriptionService: UserSubscriptionService, | ||
28 | private notifier: Notifier | ||
29 | ) {} | ||
30 | |||
31 | ngOnInit () { | ||
32 | if (this.route.snapshot.queryParams['search']) { | ||
33 | this.search = this.route.snapshot.queryParams['search'] | ||
34 | } | ||
35 | } | ||
36 | |||
37 | onNearOfBottom () { | ||
38 | // Last page | ||
39 | if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return | ||
40 | |||
41 | this.pagination.currentPage += 1 | ||
42 | this.loadFollowers() | ||
43 | } | ||
44 | |||
45 | onSearch (search: string) { | ||
46 | this.search = search | ||
47 | this.loadFollowers(false) | ||
48 | } | ||
49 | |||
50 | isFollowingAccount (follow: ActorFollow) { | ||
51 | return follow.following.name === this.getUsername() | ||
52 | } | ||
53 | |||
54 | private loadFollowers (more = true) { | ||
55 | this.userSubscriptionService.listFollowers({ | ||
56 | pagination: this.pagination, | ||
57 | nameWithHost: this.getUsername(), | ||
58 | search: this.search | ||
59 | }).subscribe({ | ||
60 | next: res => { | ||
61 | this.follows = more | ||
62 | ? this.follows.concat(res.data) | ||
63 | : res.data | ||
64 | this.pagination.totalItems = res.total | ||
65 | |||
66 | this.onDataSubject.next(res.data) | ||
67 | }, | ||
68 | |||
69 | error: err => this.notifier.error(err.message) | ||
70 | }) | ||
71 | } | ||
72 | |||
73 | private getUsername () { | ||
74 | return this.auth.getUser().username | ||
75 | } | ||
76 | } | ||
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.html b/client/src/app/+my-library/my-follows/my-subscriptions.component.html new file mode 100644 index 000000000..775f0e783 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.html | |||
@@ -0,0 +1,36 @@ | |||
1 | <h1> | ||
2 | <span> | ||
3 | <my-global-icon iconName="subscriptions" aria-hidden="true"></my-global-icon> | ||
4 | <ng-container i18n>My subscriptions</ng-container> | ||
5 | <span class="badge badge-secondary"> {{ pagination.totalItems }}</span> | ||
6 | </span> | ||
7 | </h1> | ||
8 | |||
9 | <div class="video-subscriptions-header"> | ||
10 | <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> | ||
11 | </div> | ||
12 | |||
13 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscription yet.</div> | ||
14 | |||
15 | <div class="actors" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> | ||
16 | <div *ngFor="let videoChannel of videoChannels" class="actor"> | ||
17 | <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/c', videoChannel.nameWithHost ]"></my-actor-avatar> | ||
18 | |||
19 | <div class="actor-info"> | ||
20 | <a [routerLink]="[ '/c', videoChannel.nameWithHost ]" class="actor-names" i18n-title title="Channel page"> | ||
21 | <div class="actor-display-name">{{ videoChannel.displayName }}</div> | ||
22 | <div class="actor-name">{{ videoChannel.nameWithHost }}</div> | ||
23 | </a> | ||
24 | |||
25 | <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div> | ||
26 | |||
27 | <a [routerLink]="[ '/a', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner"> | ||
28 | <span i18n>Created by {{ videoChannel.ownerBy }}</span> | ||
29 | |||
30 | <my-actor-avatar [account]="videoChannel.ownerAccount" size="18"></my-actor-avatar> | ||
31 | </a> | ||
32 | </div> | ||
33 | |||
34 | <my-subscribe-button [videoChannels]="[videoChannel]"></my-subscribe-button> | ||
35 | </div> | ||
36 | </div> | ||
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.scss b/client/src/app/+my-library/my-follows/my-subscriptions.component.scss new file mode 100644 index 000000000..310e11cb0 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.scss | |||
@@ -0,0 +1,16 @@ | |||
1 | @use '_variables' as *; | ||
2 | @use '_mixins' as *; | ||
3 | @use '_actor' as *; | ||
4 | |||
5 | .video-subscriptions-header { | ||
6 | margin-bottom: 30px; | ||
7 | display: flex; | ||
8 | } | ||
9 | |||
10 | input[type=text] { | ||
11 | @include peertube-input-text(300px); | ||
12 | } | ||
13 | |||
14 | .actor { | ||
15 | @include actor-row; | ||
16 | } | ||
diff --git a/client/src/app/+my-library/my-follows/my-subscriptions.component.ts b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts new file mode 100644 index 000000000..f676aa014 --- /dev/null +++ b/client/src/app/+my-library/my-follows/my-subscriptions.component.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { Subject } from 'rxjs' | ||
2 | import { Component } from '@angular/core' | ||
3 | import { ComponentPagination, Notifier } from '@app/core' | ||
4 | import { VideoChannel } from '@app/shared/shared-main' | ||
5 | import { UserSubscriptionService } from '@app/shared/shared-user-subscription' | ||
6 | |||
7 | @Component({ | ||
8 | templateUrl: './my-subscriptions.component.html', | ||
9 | styleUrls: [ './my-subscriptions.component.scss' ] | ||
10 | }) | ||
11 | export class MySubscriptionsComponent { | ||
12 | videoChannels: VideoChannel[] = [] | ||
13 | |||
14 | pagination: ComponentPagination = { | ||
15 | currentPage: 1, | ||
16 | itemsPerPage: 10, | ||
17 | totalItems: null | ||
18 | } | ||
19 | |||
20 | onDataSubject = new Subject<any[]>() | ||
21 | |||
22 | search: string | ||
23 | |||
24 | constructor ( | ||
25 | private userSubscriptionService: UserSubscriptionService, | ||
26 | private notifier: Notifier | ||
27 | ) {} | ||
28 | |||
29 | onNearOfBottom () { | ||
30 | // Last page | ||
31 | if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return | ||
32 | |||
33 | this.pagination.currentPage += 1 | ||
34 | this.loadSubscriptions() | ||
35 | } | ||
36 | |||
37 | onSearch (search: string) { | ||
38 | this.search = search | ||
39 | this.loadSubscriptions(false) | ||
40 | } | ||
41 | |||
42 | private loadSubscriptions (more = true) { | ||
43 | this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search }) | ||
44 | .subscribe({ | ||
45 | next: res => { | ||
46 | this.videoChannels = more | ||
47 | ? this.videoChannels.concat(res.data) | ||
48 | : res.data | ||
49 | this.pagination.totalItems = res.total | ||
50 | |||
51 | this.onDataSubject.next(res.data) | ||
52 | }, | ||
53 | |||
54 | error: err => this.notifier.error(err.message) | ||
55 | }) | ||
56 | } | ||
57 | } | ||