Add list of instance follows in about page
authorChocobozzz <me@florianbigard.com>
Thu, 6 Jun 2019 09:39:22 +0000 (11:39 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 6 Jun 2019 09:43:01 +0000 (11:43 +0200)
16 files changed:
client/src/app/+about/about-follows/about-follows.component.html [new file with mode: 0644]
client/src/app/+about/about-follows/about-follows.component.scss [new file with mode: 0644]
client/src/app/+about/about-follows/about-follows.component.ts [new file with mode: 0644]
client/src/app/+about/about-routing.module.ts
client/src/app/+about/about.component.html
client/src/app/+about/about.module.ts
client/src/app/+admin/admin.module.ts
client/src/app/+admin/follows/followers-list/followers-list.component.ts
client/src/app/+admin/follows/following-add/following-add.component.ts
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/+admin/follows/index.ts
client/src/app/+admin/follows/shared/follow.service.ts [deleted file]
client/src/app/+admin/follows/shared/index.ts [deleted file]
client/src/app/shared/instance/follow.service.ts [new file with mode: 0644]
client/src/app/shared/shared.module.ts
client/src/app/shared/video/abstract-video-list.html

diff --git a/client/src/app/+about/about-follows/about-follows.component.html b/client/src/app/+about/about-follows/about-follows.component.html
new file mode 100644 (file)
index 0000000..18689bb
--- /dev/null
@@ -0,0 +1,22 @@
+<div class="row" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
+  <div class="col-xl-6 col-md-12">
+    <div i18n class="subtitle">Followers</div>
+
+    <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have followers.</div>
+
+    <a *ngFor="let follower of followers" [href]="buildLink(follower)" target="_blank" rel="noopener noreferrer">
+      {{ follower }}
+    </a>
+  </div>
+
+  <div class="col-xl-6 col-md-12">
+    <div i18n class="subtitle">Followings</div>
+
+    <div i18n class="no-results" *ngIf="followingsPagination.totalItems === 0">This instance does not have followings.</div>
+
+    <a *ngFor="let following of followings" [href]="buildLink(following)" target="_blank" rel="noopener noreferrer">
+      {{ following }}
+    </a>
+  </div>
+
+</div>
diff --git a/client/src/app/+about/about-follows/about-follows.component.scss b/client/src/app/+about/about-follows/about-follows.component.scss
new file mode 100644 (file)
index 0000000..e0d597a
--- /dev/null
@@ -0,0 +1,14 @@
+@import '_variables';
+@import '_mixins';
+
+.subtitle {
+  font-size: 18px;
+  font-weight: $font-semibold;
+  margin-bottom: 20px;
+}
+
+a {
+  display: block;
+  width: fit-content;
+  margin-top: 3px;
+}
diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts
new file mode 100644 (file)
index 0000000..f0e1375
--- /dev/null
@@ -0,0 +1,103 @@
+import { Component, OnInit } from '@angular/core'
+import { FollowService } from '@app/shared/instance/follow.service'
+import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
+import { Notifier } from '@app/core'
+import { RestService } from '@app/shared'
+import { SortMeta } from 'primeng/api'
+
+@Component({
+  selector: 'my-about-follows',
+  templateUrl: './about-follows.component.html',
+  styleUrls: [ './about-follows.component.scss' ]
+})
+
+export class AboutFollowsComponent implements OnInit {
+  followers: string[] = []
+  followings: string[] = []
+
+  followersPagination: ComponentPagination = {
+    currentPage: 1,
+    itemsPerPage: 40,
+    totalItems: null
+  }
+
+  followingsPagination: ComponentPagination = {
+    currentPage: 1,
+    itemsPerPage: 40,
+    totalItems: null
+  }
+
+  sort: SortMeta = {
+    field: 'createdAt',
+    order: -1
+  }
+
+  constructor (
+    private restService: RestService,
+    private notifier: Notifier,
+    private followService: FollowService
+  ) { }
+
+  ngOnInit () {
+    this.loadMoreFollowers()
+
+    this.loadMoreFollowings()
+  }
+
+  onNearOfBottom () {
+    this.onNearOfFollowersBottom()
+
+    this.onNearOfFollowingsBottom()
+  }
+
+  onNearOfFollowersBottom () {
+    if (!hasMoreItems(this.followersPagination)) return
+
+    this.followersPagination.currentPage += 1
+    this.loadMoreFollowers()
+  }
+
+  onNearOfFollowingsBottom () {
+    if (!hasMoreItems(this.followingsPagination)) return
+
+    this.followingsPagination.currentPage += 1
+    this.loadMoreFollowings()
+  }
+
+  buildLink (host: string) {
+    return window.location.protocol + '//' + host
+  }
+
+  private loadMoreFollowers () {
+    const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination)
+
+    this.followService.getFollowers(pagination, this.sort)
+        .subscribe(
+          resultList => {
+            const newFollowers = resultList.data.map(r => r.follower.host)
+            this.followers = this.followers.concat(newFollowers)
+
+            this.followersPagination.totalItems = resultList.total
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+
+  private loadMoreFollowings () {
+    const pagination = this.restService.componentPaginationToRestPagination(this.followingsPagination)
+
+    this.followService.getFollowing(pagination, this.sort)
+        .subscribe(
+          resultList => {
+            const newFollowings = resultList.data.map(r => r.following.host)
+            this.followings = this.followings.concat(newFollowings)
+
+            this.followingsPagination.totalItems = resultList.total
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+
+}
index c83c62c7f264bafb90eb3d726fe626f58bf1e5c6..33e5070cb914070c2aed198ed2e0a27521b2fecb 100644 (file)
@@ -4,6 +4,7 @@ import { MetaGuard } from '@ngx-meta/core'
 import { AboutComponent } from './about.component'
 import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
 import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
+import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
 
 const aboutRoutes: Routes = [
   {
@@ -33,6 +34,15 @@ const aboutRoutes: Routes = [
             title: 'About PeerTube'
           }
         }
+      },
+      {
+        path: 'follows',
+        component: AboutFollowsComponent,
+        data: {
+          meta: {
+            title: 'About follows'
+          }
+        }
       }
     ]
   }
index 8c50835c1c1dca176eead46433aab3f84bba13e3..0c4a5156d6bee85c177585e4474117c3021cb2ed 100644 (file)
@@ -5,10 +5,12 @@
       <a i18n routerLink="instance" routerLinkActive="active" class="title-page">Instance</a>
 
       <a i18n routerLink="peertube" routerLinkActive="active" class="title-page">PeerTube</a>
+
+      <a i18n routerLink="follows" routerLinkActive="active" class="title-page">Follows</a>
     </div>
   </div>
 
   <div class="margin-content">
     <router-outlet></router-outlet>
   </div>
-</div>
\ No newline at end of file
+</div>
index 9c6b29740dd4f833f044e1c84506709474c7aeff..49a7a52f848ad2b1813f5cc505c740eea54b339c 100644 (file)
@@ -6,6 +6,7 @@ import { SharedModule } from '../shared'
 import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
 import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
 import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
+import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
 
 @NgModule({
   imports: [
@@ -17,6 +18,7 @@ import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-a
     AboutComponent,
     AboutInstanceComponent,
     AboutPeertubeComponent,
+    AboutFollowsComponent,
     ContactAdminModalComponent
   ],
 
index 71a4dfc4a588d547bc18ecd165b588fe38e93080..9ab883f60c4578e00775e9b37d0121ea6ead3d2f 100644 (file)
@@ -5,7 +5,7 @@ import { TableModule } from 'primeng/table'
 import { SharedModule } from '../shared'
 import { AdminRoutingModule } from './admin-routing.module'
 import { AdminComponent } from './admin.component'
-import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows'
+import { FollowersListComponent, FollowingAddComponent, FollowsComponent } from './follows'
 import { FollowingListComponent } from './follows/following-list/following-list.component'
 import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users'
 import {
@@ -66,7 +66,6 @@ import { DebugComponent, DebugService } from '@app/+admin/system/debug'
   ],
 
   providers: [
-    FollowService,
     RedundancyService,
     JobService,
     LogsService,
index b78cdf656edfd76b3f2c94d341dd87c72553ced0..e25d9ab66f5a3158f03fb7dca9a7510c325eec27 100644 (file)
@@ -3,7 +3,7 @@ import { ConfirmService, Notifier } from '@app/core'
 import { SortMeta } from 'primeng/primeng'
 import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
 import { RestPagination, RestTable } from '../../../shared'
-import { FollowService } from '../shared'
+import { FollowService } from '@app/shared/instance/follow.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
index 2bb2497466d21c05c70182b5ca9f81c6ac3aa4b4..308bbb0c58e87dc60d2b7bdf60247b43137da0a9 100644 (file)
@@ -3,7 +3,7 @@ import { Router } from '@angular/router'
 import { Notifier } from '@app/core'
 import { ConfirmService } from '../../../core'
 import { validateHost } from '../../../shared'
-import { FollowService } from '../shared'
+import { FollowService } from '@app/shared/instance/follow.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
index 4517a721e1494471e4874d52def6e35e04c6319f..ded616624d02165bf3ce4691fa1c6d1e48914a14 100644 (file)
@@ -4,7 +4,7 @@ import { SortMeta } from 'primeng/primeng'
 import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
 import { ConfirmService } from '../../../core/confirm/confirm.service'
 import { RestPagination, RestTable } from '../../../shared'
-import { FollowService } from '../shared'
+import { FollowService } from '@app/shared/instance/follow.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
index 7849a06e7253a3de5151b7d315b52744b321dc4b..e94f33710513285583899589d824c7d7f9200d7c 100644 (file)
@@ -1,6 +1,5 @@
 export * from './following-add'
 export * from './followers-list'
 export * from './following-list'
-export * from './shared'
 export * from './follows.component'
 export * from './follows.routes'
diff --git a/client/src/app/+admin/follows/shared/follow.service.ts b/client/src/app/+admin/follows/shared/follow.service.ts
deleted file mode 100644 (file)
index c2b8ef0..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-import { catchError, map } from 'rxjs/operators'
-import { HttpClient, HttpParams } from '@angular/common/http'
-import { Injectable } from '@angular/core'
-import { SortMeta } from 'primeng/primeng'
-import { Observable } from 'rxjs'
-import { ActorFollow, ResultList } from '../../../../../../shared'
-import { environment } from '../../../../environments/environment'
-import { RestExtractor, RestPagination, RestService } from '../../../shared'
-
-@Injectable()
-export class FollowService {
-  private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server'
-
-  constructor (
-    private authHttp: HttpClient,
-    private restService: RestService,
-    private restExtractor: RestExtractor
-  ) {
-  }
-
-  getFollowing (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> {
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    if (search) params = params.append('search', search)
-
-    return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params })
-               .pipe(
-                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> {
-    let params = new HttpParams()
-    params = this.restService.addRestGetParams(params, pagination, sort)
-
-    if (search) params = params.append('search', search)
-
-    return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params })
-               .pipe(
-                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  follow (notEmptyHosts: string[]) {
-    const body = {
-      hosts: notEmptyHosts
-    }
-
-    return this.authHttp.post(FollowService.BASE_APPLICATION_URL + '/following', body)
-               .pipe(
-                 map(this.restExtractor.extractDataBool),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  unfollow (follow: ActorFollow) {
-    return this.authHttp.delete(FollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host)
-               .pipe(
-                 map(this.restExtractor.extractDataBool),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  acceptFollower (follow: ActorFollow) {
-    const handle = follow.follower.name + '@' + follow.follower.host
-
-    return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
-               .pipe(
-                 map(this.restExtractor.extractDataBool),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  rejectFollower (follow: ActorFollow) {
-    const handle = follow.follower.name + '@' + follow.follower.host
-
-    return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
-               .pipe(
-                 map(this.restExtractor.extractDataBool),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-
-  removeFollower (follow: ActorFollow) {
-    const handle = follow.follower.name + '@' + follow.follower.host
-
-    return this.authHttp.delete(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}`)
-               .pipe(
-                 map(this.restExtractor.extractDataBool),
-                 catchError(res => this.restExtractor.handleError(res))
-               )
-  }
-}
diff --git a/client/src/app/+admin/follows/shared/index.ts b/client/src/app/+admin/follows/shared/index.ts
deleted file mode 100644 (file)
index 78d456d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './follow.service'
diff --git a/client/src/app/shared/instance/follow.service.ts b/client/src/app/shared/instance/follow.service.ts
new file mode 100644 (file)
index 0000000..5a44c64
--- /dev/null
@@ -0,0 +1,96 @@
+import { catchError, map } from 'rxjs/operators'
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import { SortMeta } from 'primeng/primeng'
+import { Observable } from 'rxjs'
+import { ActorFollow, ResultList } from '@shared/index'
+import { environment } from '../../../environments/environment'
+import { RestExtractor, RestPagination, RestService } from '../rest'
+
+@Injectable()
+export class FollowService {
+  private static BASE_APPLICATION_URL = 'https://peertube2.cpy.re' + '/api/v1/server'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restService: RestService,
+    private restExtractor: RestExtractor
+  ) {
+  }
+
+  getFollowing (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    if (search) params = params.append('search', search)
+
+    return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params })
+               .pipe(
+                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    if (search) params = params.append('search', search)
+
+    return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params })
+               .pipe(
+                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  follow (notEmptyHosts: string[]) {
+    const body = {
+      hosts: notEmptyHosts
+    }
+
+    return this.authHttp.post(FollowService.BASE_APPLICATION_URL + '/following', body)
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  unfollow (follow: ActorFollow) {
+    return this.authHttp.delete(FollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host)
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  acceptFollower (follow: ActorFollow) {
+    const handle = follow.follower.name + '@' + follow.follower.host
+
+    return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  rejectFollower (follow: ActorFollow) {
+    const handle = follow.follower.name + '@' + follow.follower.host
+
+    return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  removeFollower (follow: ActorFollow) {
+    const handle = follow.follower.name + '@' + follow.follower.host
+
+    return this.authHttp.delete(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}`)
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+}
index 39f1a69e2223fccc8de326989c36478d256a98dd..1d49c7bc85d965e80987599c5eb5055ca1be89d4 100644 (file)
@@ -85,6 +85,7 @@ import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklis
 import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
 import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
 import { ClipboardModule } from 'ngx-clipboard'
+import { FollowService } from '@app/shared/instance/follow.service'
 
 @NgModule({
   imports: [
@@ -271,6 +272,8 @@ import { ClipboardModule } from 'ngx-clipboard'
 
     UserNotificationService,
 
+    FollowService,
+
     I18n
   ]
 })
index 11cf1bd9230a3a8cc0d3d14c6a4f1d3c57ee7891..efd369bcadef871858403802304ac251dd22e0d8 100644 (file)
@@ -27,7 +27,6 @@
         {{ getCurrentGroupedDateLabel(video) }}
       </div>
 
-
       <my-video-miniature
         [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
         [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions"