diff options
author | Chocobozzz <me@florianbigard.com> | 2020-06-23 14:10:17 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-06-23 16:00:49 +0200 |
commit | 67ed6552b831df66713bac9e672738796128d33f (patch) | |
tree | 59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared/user-subscription | |
parent | 0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff) | |
download | PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.gz PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.zst PeerTube-67ed6552b831df66713bac9e672738796128d33f.zip |
Reorganize client shared modules
Diffstat (limited to 'client/src/app/shared/user-subscription')
8 files changed, 0 insertions, 643 deletions
diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/user-subscription/index.ts deleted file mode 100644 index e76940f7b..000000000 --- a/client/src/app/shared/user-subscription/index.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | export * from './user-subscription.service' | ||
2 | export * from './subscribe-button.component' | ||
3 | export * from './remote-subscribe.component' | ||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/user-subscription/remote-subscribe.component.html deleted file mode 100644 index acfec0a8e..000000000 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.html +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | <form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> | ||
2 | <div class="form-group mb-2"> | ||
3 | <input type="email" | ||
4 | formControlName="text" | ||
5 | class="form-control" | ||
6 | (keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()" | ||
7 | placeholder="jane_doe@example.com"> | ||
8 | </div> | ||
9 | |||
10 | <button type="submit" [disabled]="!form.valid" class="btn btn-sm btn-remote-follow" i18n> | ||
11 | <span *ngIf="!interact">Remote subscribe</span> | ||
12 | <span *ngIf="interact">Remote interact</span> | ||
13 | </button> | ||
14 | |||
15 | <my-help *ngIf="!interact && showHelp"> | ||
16 | <ng-template ptTemplate="customHtml"> | ||
17 | <ng-container i18n> | ||
18 | You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br /> | ||
19 | For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
22 | </my-help> | ||
23 | |||
24 | <my-help *ngIf="showHelp && interact"> | ||
25 | <ng-template ptTemplate="customHtml"> | ||
26 | <ng-container i18n> | ||
27 | You can interact with this via any ActivityPub-capable fediverse instance.<br /><br /> | ||
28 | For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there. | ||
29 | </ng-container> | ||
30 | </ng-template> | ||
31 | </my-help> | ||
32 | </form> | ||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.scss b/client/src/app/shared/user-subscription/remote-subscribe.component.scss deleted file mode 100644 index 698c5866a..000000000 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.scss +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | @import '_mixins'; | ||
2 | |||
3 | .btn-remote-follow { | ||
4 | @include peertube-button; | ||
5 | @include orange-button; | ||
6 | } \ No newline at end of file | ||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.ts b/client/src/app/shared/user-subscription/remote-subscribe.component.ts deleted file mode 100644 index befdb7157..000000000 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.ts +++ /dev/null | |||
@@ -1,62 +0,0 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { FormReactive } from '@app/shared/forms/form-reactive' | ||
3 | import { | ||
4 | FormValidatorService, | ||
5 | UserValidatorsService | ||
6 | } from '@app/shared/forms/form-validators' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-remote-subscribe', | ||
10 | templateUrl: './remote-subscribe.component.html', | ||
11 | styleUrls: ['./remote-subscribe.component.scss'] | ||
12 | }) | ||
13 | export class RemoteSubscribeComponent extends FormReactive implements OnInit { | ||
14 | @Input() uri: string | ||
15 | @Input() interact = false | ||
16 | @Input() showHelp = false | ||
17 | |||
18 | constructor ( | ||
19 | protected formValidatorService: FormValidatorService, | ||
20 | private userValidatorsService: UserValidatorsService | ||
21 | ) { | ||
22 | super() | ||
23 | } | ||
24 | |||
25 | ngOnInit () { | ||
26 | this.buildForm({ | ||
27 | text: this.userValidatorsService.USER_EMAIL | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | onValidKey () { | ||
32 | this.check() | ||
33 | if (!this.form.valid) return | ||
34 | |||
35 | this.formValidated() | ||
36 | } | ||
37 | |||
38 | formValidated () { | ||
39 | const address = this.form.value['text'] | ||
40 | const [ username, hostname ] = address.split('@') | ||
41 | |||
42 | // Should not have CORS error because https://tools.ietf.org/html/rfc7033#section-5 | ||
43 | fetch(`https://${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`) | ||
44 | .then(response => response.json()) | ||
45 | .then(data => new Promise((resolve, reject) => { | ||
46 | console.log(data) | ||
47 | |||
48 | if (data && Array.isArray(data.links)) { | ||
49 | const link: { template: string } = data.links.find((link: any) => { | ||
50 | return link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe' | ||
51 | }) | ||
52 | |||
53 | if (link && link.template.includes('{uri}')) { | ||
54 | resolve(link.template.replace('{uri}', encodeURIComponent(this.uri))) | ||
55 | } | ||
56 | } | ||
57 | reject() | ||
58 | })) | ||
59 | .then(window.open) | ||
60 | .catch(err => console.error(err)) | ||
61 | } | ||
62 | } | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/user-subscription/subscribe-button.component.html deleted file mode 100644 index 85b3d1fdb..000000000 --- a/client/src/app/shared/user-subscription/subscribe-button.component.html +++ /dev/null | |||
@@ -1,67 +0,0 @@ | |||
1 | <div class="btn-group-subscribe btn-group" | ||
2 | [ngClass]="{'subscribe-button': !isAllChannelsSubscribed, 'unsubscribe-button': isAllChannelsSubscribed, 'big': isBigButton }"> | ||
3 | |||
4 | <ng-template #userLoggedOut> | ||
5 | <span [ngClass]="{ 'extra-text': isAtLeastOneChannelSubscribed }"> | ||
6 | <ng-container *ngIf="account; then multiple; else single"></ng-container> | ||
7 | <ng-template i18n #single>Subscribe</ng-template> | ||
8 | <ng-template #multiple> | ||
9 | <span i18n>Subscribe to all channels</span> | ||
10 | <span *ngIf="isAtLeastOneChannelSubscribed">{{ subscribeStatus(true).length }}/{{ subscribed.size }} | ||
11 | <ng-container i18n>channels subscribed</ng-container> | ||
12 | </span> | ||
13 | </ng-template> | ||
14 | </span> | ||
15 | <span *ngIf="!isBigButton && displayFollowers && videoChannels.length > 1 && videoChannel.followersCount !== 0" class="followers-count"> | ||
16 | {{ videoChannels[0].followersCount | myNumberFormatter }} | ||
17 | </span> | ||
18 | </ng-template> | ||
19 | |||
20 | <ng-template #userLoggedIn> | ||
21 | <button *ngIf="!isAllChannelsSubscribed" type="button" | ||
22 | class="btn btn-sm" role="button" | ||
23 | (click)="subscribe()"> | ||
24 | <ng-template [ngTemplateOutlet]="userLoggedOut"></ng-template> | ||
25 | </button> | ||
26 | |||
27 | <button | ||
28 | *ngIf="isAllChannelsSubscribed" type="button" | ||
29 | class="btn btn-sm" role="button" | ||
30 | (click)="unsubscribe()"> | ||
31 | <ng-container i18n>{account + "", select, undefined {Unsubscribe} other {Unsubscribe from all channels}}</ng-container> | ||
32 | </button> | ||
33 | </ng-template> | ||
34 | |||
35 | <ng-container | ||
36 | *ngIf="isUserLoggedIn(); then userLoggedIn"> | ||
37 | </ng-container> | ||
38 | |||
39 | <div class="btn-group" ngbDropdown autoClose="outside" | ||
40 | placement="bottom-right" role="group" | ||
41 | aria-label="Multiple ways to subscribe to the current channel"> | ||
42 | <button class="btn btn-sm dropdown-toggle-split" ngbDropdownToggle> | ||
43 | <ng-container | ||
44 | *ngIf="!isUserLoggedIn(); then userLoggedOut"> | ||
45 | </ng-container> | ||
46 | </button> | ||
47 | |||
48 | <div class="dropdown-menu" ngbDropdownMenu> | ||
49 | |||
50 | <h6 class="dropdown-header" i18n>Using an ActivityPub account</h6> | ||
51 | |||
52 | <button class="dropdown-item" (click)="subscribe()"> | ||
53 | <span *ngIf="!isUserLoggedIn()" i18n>Subscribe with an account on this instance</span> | ||
54 | <span *ngIf="isUserLoggedIn()" i18n>Subscribe with your local account</span> | ||
55 | </button> | ||
56 | |||
57 | <button class="dropdown-item dropdown-item-neutral" i18n>Subscribe with a Mastodon account:</button> | ||
58 | <my-remote-subscribe [showHelp]="true" [uri]="uri"></my-remote-subscribe> | ||
59 | |||
60 | <div class="dropdown-divider"></div> | ||
61 | |||
62 | <h6 class="dropdown-header" i18n>Using a syndication feed</h6> | ||
63 | <a [href]="rssUri" target="_blank" class="dropdown-item" i18n>Subscribe via RSS</a> | ||
64 | |||
65 | </div> | ||
66 | </div> | ||
67 | </div> | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/user-subscription/subscribe-button.component.scss deleted file mode 100644 index b739c5ae2..000000000 --- a/client/src/app/shared/user-subscription/subscribe-button.component.scss +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .btn-group-subscribe { | ||
5 | @include peertube-button; | ||
6 | @include disable-default-a-behaviour; | ||
7 | |||
8 | float: right; | ||
9 | padding: 0; | ||
10 | |||
11 | & > .btn, | ||
12 | & > .dropdown > .dropdown-toggle { | ||
13 | font-size: 15px; | ||
14 | } | ||
15 | |||
16 | &:not(.big) { | ||
17 | white-space: nowrap; | ||
18 | } | ||
19 | |||
20 | &.big { | ||
21 | height: 35px; | ||
22 | |||
23 | & > button:first-child { | ||
24 | width: 175px; | ||
25 | } | ||
26 | |||
27 | button .extra-text { | ||
28 | span:first-child { | ||
29 | line-height: 80%; | ||
30 | } | ||
31 | |||
32 | span:not(:first-child) { | ||
33 | font-size: 75%; | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | // Unlogged | ||
39 | & > .dropdown > .dropdown-toggle span { | ||
40 | padding-right: 3px; | ||
41 | } | ||
42 | |||
43 | // Logged | ||
44 | & > .btn { | ||
45 | padding-right: 4px; | ||
46 | |||
47 | & + .dropdown > button { | ||
48 | padding-left: 2px; | ||
49 | |||
50 | &::after { | ||
51 | position: relative; | ||
52 | top: 1px; | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | &.subscribe-button { | ||
58 | .btn { | ||
59 | @include orange-button; | ||
60 | font-weight: 600; | ||
61 | } | ||
62 | |||
63 | span.followers-count { | ||
64 | padding-left: 5px; | ||
65 | } | ||
66 | } | ||
67 | &.unsubscribe-button { | ||
68 | .btn { | ||
69 | @include grey-button; | ||
70 | font-weight: 600; | ||
71 | } | ||
72 | } | ||
73 | |||
74 | .dropdown-menu { | ||
75 | cursor: default; | ||
76 | |||
77 | button { | ||
78 | cursor: pointer; | ||
79 | } | ||
80 | |||
81 | .dropdown-item-neutral { | ||
82 | cursor: default; | ||
83 | |||
84 | &:hover, | ||
85 | &:focus { | ||
86 | background-color: inherit; | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | ::ng-deep form { | ||
92 | padding: 0.25rem 1rem; | ||
93 | } | ||
94 | |||
95 | input { | ||
96 | @include peertube-input-text(100%); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | .extra-text { | ||
101 | display: flex; | ||
102 | flex-direction: column; | ||
103 | |||
104 | span:first-child { | ||
105 | line-height: 75%; | ||
106 | } | ||
107 | |||
108 | span:not(:first-child) { | ||
109 | font-size: 60%; | ||
110 | text-align: left; | ||
111 | } | ||
112 | } | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/user-subscription/subscribe-button.component.ts deleted file mode 100644 index 947f34c85..000000000 --- a/client/src/app/shared/user-subscription/subscribe-button.component.ts +++ /dev/null | |||
@@ -1,198 +0,0 @@ | |||
1 | import { Component, Input, OnInit, OnChanges } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | ||
3 | import { AuthService, Notifier } from '@app/core' | ||
4 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | ||
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | import { VideoService } from '@app/shared/video/video.service' | ||
8 | import { FeedFormat } from '../../../../../shared/models/feeds' | ||
9 | import { Account } from '@app/shared/account/account.model' | ||
10 | import { concat, forkJoin, merge } from 'rxjs' | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-subscribe-button', | ||
14 | templateUrl: './subscribe-button.component.html', | ||
15 | styleUrls: [ './subscribe-button.component.scss' ] | ||
16 | }) | ||
17 | export class SubscribeButtonComponent implements OnInit, OnChanges { | ||
18 | /** | ||
19 | * SubscribeButtonComponent can be used with a single VideoChannel passed as [VideoChannel], | ||
20 | * or with an account and a full list of that account's videoChannels. The latter is intended | ||
21 | * to allow mass un/subscription from an account's page, while keeping the channel-centric | ||
22 | * subscription model. | ||
23 | */ | ||
24 | @Input() account: Account | ||
25 | @Input() videoChannels: VideoChannel[] | ||
26 | @Input() displayFollowers = false | ||
27 | @Input() size: 'small' | 'normal' = 'normal' | ||
28 | |||
29 | subscribed = new Map<string, boolean>() | ||
30 | |||
31 | constructor ( | ||
32 | private authService: AuthService, | ||
33 | private router: Router, | ||
34 | private notifier: Notifier, | ||
35 | private userSubscriptionService: UserSubscriptionService, | ||
36 | private i18n: I18n, | ||
37 | private videoService: VideoService | ||
38 | ) { } | ||
39 | |||
40 | get handle () { | ||
41 | return this.account | ||
42 | ? this.account.nameWithHost | ||
43 | : this.videoChannel.name + '@' + this.videoChannel.host | ||
44 | } | ||
45 | |||
46 | get channelHandle () { | ||
47 | return this.getChannelHandler(this.videoChannel) | ||
48 | } | ||
49 | |||
50 | get uri () { | ||
51 | return this.account | ||
52 | ? this.account.url | ||
53 | : this.videoChannels[0].url | ||
54 | } | ||
55 | |||
56 | get rssUri () { | ||
57 | const rssFeed = this.account | ||
58 | ? this.videoService | ||
59 | .getAccountFeedUrls(this.account.id) | ||
60 | .find(i => i.format === FeedFormat.RSS) | ||
61 | : this.videoService | ||
62 | .getVideoChannelFeedUrls(this.videoChannels[0].id) | ||
63 | .find(i => i.format === FeedFormat.RSS) | ||
64 | |||
65 | return rssFeed.url | ||
66 | } | ||
67 | |||
68 | get videoChannel () { | ||
69 | return this.videoChannels[0] | ||
70 | } | ||
71 | |||
72 | get isAllChannelsSubscribed () { | ||
73 | return this.subscribeStatus(true).length === this.videoChannels.length | ||
74 | } | ||
75 | |||
76 | get isAtLeastOneChannelSubscribed () { | ||
77 | return this.subscribeStatus(true).length > 0 | ||
78 | } | ||
79 | |||
80 | get isBigButton () { | ||
81 | return this.isUserLoggedIn() && this.videoChannels.length > 1 && this.isAtLeastOneChannelSubscribed | ||
82 | } | ||
83 | |||
84 | ngOnInit () { | ||
85 | this.loadSubscribedStatus() | ||
86 | } | ||
87 | |||
88 | ngOnChanges () { | ||
89 | this.ngOnInit() | ||
90 | } | ||
91 | |||
92 | subscribe () { | ||
93 | if (this.isUserLoggedIn()) { | ||
94 | return this.localSubscribe() | ||
95 | } | ||
96 | |||
97 | return this.gotoLogin() | ||
98 | } | ||
99 | |||
100 | localSubscribe () { | ||
101 | const subscribedStatus = this.subscribeStatus(false) | ||
102 | |||
103 | const observableBatch = this.videoChannels | ||
104 | .map(videoChannel => this.getChannelHandler(videoChannel)) | ||
105 | .filter(handle => subscribedStatus.includes(handle)) | ||
106 | .map(handle => this.userSubscriptionService.addSubscription(handle)) | ||
107 | |||
108 | forkJoin(observableBatch) | ||
109 | .subscribe( | ||
110 | () => { | ||
111 | this.notifier.success( | ||
112 | this.account | ||
113 | ? this.i18n( | ||
114 | 'Subscribed to all current channels of {{nameWithHost}}. You will be notified of all their new videos.', | ||
115 | { nameWithHost: this.account.displayName } | ||
116 | ) | ||
117 | : this.i18n( | ||
118 | 'Subscribed to {{nameWithHost}}. You will be notified of all their new videos.', | ||
119 | { nameWithHost: this.videoChannels[0].displayName } | ||
120 | ) | ||
121 | , | ||
122 | this.i18n('Subscribed') | ||
123 | ) | ||
124 | }, | ||
125 | |||
126 | err => this.notifier.error(err.message) | ||
127 | ) | ||
128 | } | ||
129 | |||
130 | unsubscribe () { | ||
131 | if (this.isUserLoggedIn()) { | ||
132 | this.localUnsubscribe() | ||
133 | } | ||
134 | } | ||
135 | |||
136 | localUnsubscribe () { | ||
137 | const subscribeStatus = this.subscribeStatus(true) | ||
138 | |||
139 | const observableBatch = this.videoChannels | ||
140 | .map(videoChannel => this.getChannelHandler(videoChannel)) | ||
141 | .filter(handle => subscribeStatus.includes(handle)) | ||
142 | .map(handle => this.userSubscriptionService.deleteSubscription(handle)) | ||
143 | |||
144 | concat(...observableBatch) | ||
145 | .subscribe({ | ||
146 | complete: () => { | ||
147 | this.notifier.success( | ||
148 | this.account | ||
149 | ? this.i18n('Unsubscribed from all channels of {{nameWithHost}}', { nameWithHost: this.account.nameWithHost }) | ||
150 | : this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannels[ 0 ].nameWithHost }) | ||
151 | , | ||
152 | this.i18n('Unsubscribed') | ||
153 | ) | ||
154 | }, | ||
155 | |||
156 | error: err => this.notifier.error(err.message) | ||
157 | }) | ||
158 | } | ||
159 | |||
160 | isUserLoggedIn () { | ||
161 | return this.authService.isLoggedIn() | ||
162 | } | ||
163 | |||
164 | gotoLogin () { | ||
165 | this.router.navigate([ '/login' ]) | ||
166 | } | ||
167 | |||
168 | subscribeStatus (subscribed: boolean) { | ||
169 | const accumulator: string[] = [] | ||
170 | for (const [key, value] of this.subscribed.entries()) { | ||
171 | if (value === subscribed) accumulator.push(key) | ||
172 | } | ||
173 | |||
174 | return accumulator | ||
175 | } | ||
176 | |||
177 | private getChannelHandler (videoChannel: VideoChannel) { | ||
178 | return videoChannel.name + '@' + videoChannel.host | ||
179 | } | ||
180 | |||
181 | private loadSubscribedStatus () { | ||
182 | if (!this.isUserLoggedIn()) return | ||
183 | |||
184 | for (const videoChannel of this.videoChannels) { | ||
185 | const handle = this.getChannelHandler(videoChannel) | ||
186 | this.subscribed.set(handle, false) | ||
187 | |||
188 | merge( | ||
189 | this.userSubscriptionService.listenToSubscriptionCacheChange(handle), | ||
190 | this.userSubscriptionService.doesSubscriptionExist(handle) | ||
191 | ).subscribe( | ||
192 | res => this.subscribed.set(handle, res), | ||
193 | |||
194 | err => this.notifier.error(err.message) | ||
195 | ) | ||
196 | } | ||
197 | } | ||
198 | } | ||
diff --git a/client/src/app/shared/user-subscription/user-subscription.service.ts b/client/src/app/shared/user-subscription/user-subscription.service.ts deleted file mode 100644 index 9af9ba23e..000000000 --- a/client/src/app/shared/user-subscription/user-subscription.service.ts +++ /dev/null | |||
@@ -1,163 +0,0 @@ | |||
1 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' | ||
2 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' | ||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
4 | import { Injectable, NgZone } from '@angular/core' | ||
5 | import { ResultList } from '../../../../../shared' | ||
6 | import { environment } from '../../../environments/environment' | ||
7 | import { RestExtractor, RestService } from '../rest' | ||
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 | import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' | ||
12 | import { uniq } from 'lodash-es' | ||
13 | import * as debug from 'debug' | ||
14 | import { enterZone, leaveZone } from '@app/shared/rxjs/zone' | ||
15 | |||
16 | const logger = debug('peertube:subscriptions:UserSubscriptionService') | ||
17 | |||
18 | type SubscriptionExistResult = { [ uri: string ]: boolean } | ||
19 | type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> } | ||
20 | |||
21 | @Injectable() | ||
22 | export class UserSubscriptionService { | ||
23 | static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/users/me/subscriptions' | ||
24 | |||
25 | // Use a replay subject because we "next" a value before subscribing | ||
26 | private existsSubject = new ReplaySubject<string>(1) | ||
27 | private readonly existsObservable: Observable<SubscriptionExistResult> | ||
28 | |||
29 | private myAccountSubscriptionCache: SubscriptionExistResult = {} | ||
30 | private myAccountSubscriptionCacheObservable: SubscriptionExistResultObservable = {} | ||
31 | private myAccountSubscriptionCacheSubject = new Subject<SubscriptionExistResult>() | ||
32 | |||
33 | constructor ( | ||
34 | private authHttp: HttpClient, | ||
35 | private restExtractor: RestExtractor, | ||
36 | private restService: RestService, | ||
37 | private ngZone: NgZone | ||
38 | ) { | ||
39 | this.existsObservable = merge( | ||
40 | this.existsSubject.pipe( | ||
41 | // We leave Angular zone so Protractor does not get stuck | ||
42 | bufferTime(500, leaveZone(this.ngZone, asyncScheduler)), | ||
43 | filter(uris => uris.length !== 0), | ||
44 | map(uris => uniq(uris)), | ||
45 | observeOn(enterZone(this.ngZone, asyncScheduler)), | ||
46 | switchMap(uris => this.doSubscriptionsExist(uris)), | ||
47 | share() | ||
48 | ), | ||
49 | |||
50 | this.myAccountSubscriptionCacheSubject | ||
51 | ) | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Subscription part | ||
56 | */ | ||
57 | |||
58 | deleteSubscription (nameWithHost: string) { | ||
59 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/' + nameWithHost | ||
60 | |||
61 | return this.authHttp.delete(url) | ||
62 | .pipe( | ||
63 | map(this.restExtractor.extractDataBool), | ||
64 | tap(() => { | ||
65 | this.myAccountSubscriptionCache[nameWithHost] = false | ||
66 | |||
67 | this.myAccountSubscriptionCacheSubject.next(this.myAccountSubscriptionCache) | ||
68 | }), | ||
69 | catchError(err => this.restExtractor.handleError(err)) | ||
70 | ) | ||
71 | } | ||
72 | |||
73 | addSubscription (nameWithHost: string) { | ||
74 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL | ||
75 | |||
76 | const body = { uri: nameWithHost } | ||
77 | return this.authHttp.post(url, body) | ||
78 | .pipe( | ||
79 | map(this.restExtractor.extractDataBool), | ||
80 | tap(() => { | ||
81 | this.myAccountSubscriptionCache[nameWithHost] = true | ||
82 | |||
83 | this.myAccountSubscriptionCacheSubject.next(this.myAccountSubscriptionCache) | ||
84 | }), | ||
85 | catchError(err => this.restExtractor.handleError(err)) | ||
86 | ) | ||
87 | } | ||
88 | |||
89 | listSubscriptions (componentPagination: ComponentPaginationLight): Observable<ResultList<VideoChannel>> { | ||
90 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL | ||
91 | |||
92 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | ||
93 | |||
94 | let params = new HttpParams() | ||
95 | params = this.restService.addRestGetParams(params, pagination) | ||
96 | |||
97 | return this.authHttp.get<ResultList<VideoChannelServer>>(url, { params }) | ||
98 | .pipe( | ||
99 | map(res => VideoChannelService.extractVideoChannels(res)), | ||
100 | catchError(err => this.restExtractor.handleError(err)) | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * SubscriptionExist part | ||
106 | */ | ||
107 | |||
108 | listenToMyAccountSubscriptionCacheSubject () { | ||
109 | return this.myAccountSubscriptionCacheSubject.asObservable() | ||
110 | } | ||
111 | |||
112 | listenToSubscriptionCacheChange (nameWithHost: string) { | ||
113 | if (nameWithHost in this.myAccountSubscriptionCacheObservable) { | ||
114 | return this.myAccountSubscriptionCacheObservable[ nameWithHost ] | ||
115 | } | ||
116 | |||
117 | const obs = this.existsObservable | ||
118 | .pipe( | ||
119 | filter(existsResult => existsResult[ nameWithHost ] !== undefined), | ||
120 | map(existsResult => existsResult[ nameWithHost ]) | ||
121 | ) | ||
122 | |||
123 | this.myAccountSubscriptionCacheObservable[ nameWithHost ] = obs | ||
124 | return obs | ||
125 | } | ||
126 | |||
127 | doesSubscriptionExist (nameWithHost: string) { | ||
128 | logger('Running subscription check for %d.', nameWithHost) | ||
129 | |||
130 | if (nameWithHost in this.myAccountSubscriptionCache) { | ||
131 | logger('Found cache for %d.', nameWithHost) | ||
132 | |||
133 | return of(this.myAccountSubscriptionCache[ nameWithHost ]) | ||
134 | } | ||
135 | |||
136 | this.existsSubject.next(nameWithHost) | ||
137 | |||
138 | logger('Fetching from network for %d.', nameWithHost) | ||
139 | return this.existsObservable.pipe( | ||
140 | filter(existsResult => existsResult[ nameWithHost ] !== undefined), | ||
141 | map(existsResult => existsResult[ nameWithHost ]), | ||
142 | tap(result => this.myAccountSubscriptionCache[ nameWithHost ] = result) | ||
143 | ) | ||
144 | } | ||
145 | |||
146 | private doSubscriptionsExist (uris: string[]): Observable<SubscriptionExistResult> { | ||
147 | const url = UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/exist' | ||
148 | let params = new HttpParams() | ||
149 | |||
150 | params = this.restService.addObjectParams(params, { uris }) | ||
151 | |||
152 | return this.authHttp.get<SubscriptionExistResult>(url, { params }) | ||
153 | .pipe( | ||
154 | tap(res => { | ||
155 | this.myAccountSubscriptionCache = { | ||
156 | ...this.myAccountSubscriptionCache, | ||
157 | ...res | ||
158 | } | ||
159 | }), | ||
160 | catchError(err => this.restExtractor.handleError(err)) | ||
161 | ) | ||
162 | } | ||
163 | } | ||