aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-instance
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared/shared-instance')
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.html3
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.scss10
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.ts10
-rw-r--r--client/src/app/shared/shared-instance/index.ts6
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.html107
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.scss40
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.ts81
-rw-r--r--client/src/app/shared/shared-instance/instance-follow.service.ts116
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.html101
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.scss40
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.ts22
-rw-r--r--client/src/app/shared/shared-instance/instance.service.ts88
-rw-r--r--client/src/app/shared/shared-instance/shared-instance.module.ts32
13 files changed, 656 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-instance/feature-boolean.component.html b/client/src/app/shared/shared-instance/feature-boolean.component.html
new file mode 100644
index 000000000..ccb8a30cc
--- /dev/null
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.html
@@ -0,0 +1,3 @@
1<span *ngIf="value === true" class="glyphicon glyphicon-ok" i18n-aria-label aria-label="yes"></span>
2<span *ngIf="value === false" class="glyphicon glyphicon-remove" i18n-aria-label aria-label="no"></span>
3
diff --git a/client/src/app/shared/shared-instance/feature-boolean.component.scss b/client/src/app/shared/shared-instance/feature-boolean.component.scss
new file mode 100644
index 000000000..56d08af06
--- /dev/null
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.scss
@@ -0,0 +1,10 @@
1@import '_variables';
2@import '_mixins';
3
4.glyphicon-ok {
5 color: $green;
6}
7
8.glyphicon-remove {
9 color: $red;
10}
diff --git a/client/src/app/shared/shared-instance/feature-boolean.component.ts b/client/src/app/shared/shared-instance/feature-boolean.component.ts
new file mode 100644
index 000000000..d02d513d6
--- /dev/null
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.ts
@@ -0,0 +1,10 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-feature-boolean',
5 templateUrl: './feature-boolean.component.html',
6 styleUrls: [ './feature-boolean.component.scss' ]
7})
8export class FeatureBooleanComponent {
9 @Input() value: boolean
10}
diff --git a/client/src/app/shared/shared-instance/index.ts b/client/src/app/shared/shared-instance/index.ts
new file mode 100644
index 000000000..1aeed357e
--- /dev/null
+++ b/client/src/app/shared/shared-instance/index.ts
@@ -0,0 +1,6 @@
1export * from './feature-boolean.component'
2export * from './instance-features-table.component'
3export * from './instance-follow.service'
4export * from './instance-statistics.component'
5export * from './instance.service'
6export * from './shared-instance.module'
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.html b/client/src/app/shared/shared-instance/instance-features-table.component.html
new file mode 100644
index 000000000..f6a3b7f0b
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.html
@@ -0,0 +1,107 @@
1<div class="feature-table">
2
3 <table class="table" *ngIf="serverConfig">
4 <caption i18n>Features found on this instance</caption>
5 <tr>
6 <th i18n class="label" scope="row">PeerTube version</th>
7
8 <td class="value">{{ getServerVersionAndCommit() }}</td>
9 </tr>
10
11 <tr>
12 <th i18n class="label" scope="row">
13 <div>Default NSFW/sensitive videos policy</div>
14 <div class="more-info">can be redefined by the users</div>
15 </th>
16
17 <td class="value">{{ buildNSFWLabel() }}</td>
18 </tr>
19
20 <tr>
21 <th i18n class="label" scope="row">User registration allowed</th>
22 <td>
23 <my-feature-boolean [value]="serverConfig.signup.allowed"></my-feature-boolean>
24 </td>
25 </tr>
26
27 <tr>
28 <th i18n class="label" colspan="2">Video uploads</th>
29 </tr>
30
31 <tr>
32 <th i18n class="sub-label" scope="row">Transcoding in multiple resolutions</th>
33 <td>
34 <my-feature-boolean [value]="serverConfig.transcoding.enabledResolutions.length !== 0"></my-feature-boolean>
35 </td>
36 </tr>
37
38 <tr>
39 <th i18n class="sub-label" scope="row">Video uploads</th>
40 <td>
41 <span i18n *ngIf="serverConfig.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span>
42 <span i18n *ngIf="!serverConfig.autoBlacklist.videos.ofUsers.enabled">Automatically published</span>
43 </td>
44 </tr>
45
46 <tr>
47 <th i18n class="sub-label" scope="row">Video quota</th>
48
49 <td class="value">
50 <ng-container *ngIf="initialUserVideoQuota !== -1">
51 {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container>
52
53 <my-help tooltipPlacement="auto" helpType="custom">
54 <ng-template ptTemplate="customHtml">
55 <div [innerHTML]="quotaHelpIndication"></div>
56 </ng-template>
57 </my-help>
58 </ng-container>
59
60 <ng-container i18n *ngIf="initialUserVideoQuota === -1">
61 Unlimited <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container>
62 </ng-container>
63 </td>
64 </tr>
65
66 <tr>
67 <th i18n class="label" colspan="2">Import</th>
68 </tr>
69
70 <tr>
71 <th i18n class="sub-label" scope="row">HTTP import (YouTube, Vimeo, direct URL...)</th>
72 <td>
73 <my-feature-boolean [value]="serverConfig.import.videos.http.enabled"></my-feature-boolean>
74 </td>
75 </tr>
76
77 <tr>
78 <th i18n class="sub-label" scope="row">Torrent import</th>
79 <td>
80 <my-feature-boolean [value]="serverConfig.import.videos.torrent.enabled"></my-feature-boolean>
81 </td>
82 </tr>
83
84
85 <tr>
86 <th i18n class="label" colspan="2">Player</th>
87 </tr>
88
89 <tr>
90 <th i18n class="sub-label" scope="row">P2P enabled</th>
91 <td>
92 <my-feature-boolean [value]="serverConfig.tracker.enabled"></my-feature-boolean>
93 </td>
94 </tr>
95
96 <tr>
97 <th i18n class="label" colspan="2">Search</th>
98 </tr>
99
100 <tr>
101 <th i18n class="sub-label" scope="row">Users can resolve distant content</th>
102 <td>
103 <my-feature-boolean [value]="serverConfig.search.remoteUri.users"></my-feature-boolean>
104 </td>
105 </tr>
106 </table>
107</div>
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.scss b/client/src/app/shared/shared-instance/instance-features-table.component.scss
new file mode 100644
index 000000000..a51574741
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.scss
@@ -0,0 +1,40 @@
1@import '_variables';
2@import '_mixins';
3
4table {
5 font-size: 14px;
6 color: pvar(--mainForegroundColor);
7
8 .label,
9 .sub-label {
10 min-width: 330px;
11
12 &.label {
13 font-weight: $font-semibold;
14 }
15
16 &.sub-label {
17 font-weight: $font-regular;
18 padding-left: 30px;
19 }
20
21 .more-info {
22 font-style: italic;
23 font-weight: initial;
24 font-size: 14px
25 }
26 }
27
28 td {
29 vertical-align: middle;
30 }
31
32 caption {
33 caption-side: top;
34 font-size: 15px;
35 font-weight: $font-semibold;
36 color: pvar(--mainForegroundColor);
37 }
38}
39
40
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts
new file mode 100644
index 000000000..8fd15ebad
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts
@@ -0,0 +1,81 @@
1import { Component, OnInit } from '@angular/core'
2import { ServerService } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { ServerConfig } from '@shared/models'
5
6@Component({
7 selector: 'my-instance-features-table',
8 templateUrl: './instance-features-table.component.html',
9 styleUrls: [ './instance-features-table.component.scss' ]
10})
11export class InstanceFeaturesTableComponent implements OnInit {
12 quotaHelpIndication = ''
13 serverConfig: ServerConfig
14
15 constructor (
16 private i18n: I18n,
17 private serverService: ServerService
18 ) {
19 }
20
21 get initialUserVideoQuota () {
22 return this.serverConfig.user.videoQuota
23 }
24
25 get dailyUserVideoQuota () {
26 return Math.min(this.initialUserVideoQuota, this.serverConfig.user.videoQuotaDaily)
27 }
28
29 ngOnInit () {
30 this.serverConfig = this.serverService.getTmpConfig()
31 this.serverService.getConfig()
32 .subscribe(config => {
33 this.serverConfig = config
34 this.buildQuotaHelpIndication()
35 })
36 }
37
38 buildNSFWLabel () {
39 const policy = this.serverConfig.instance.defaultNSFWPolicy
40
41 if (policy === 'do_not_list') return this.i18n('Hidden')
42 if (policy === 'blur') return this.i18n('Blurred with confirmation request')
43 if (policy === 'display') return this.i18n('Displayed')
44 }
45
46 getServerVersionAndCommit () {
47 return this.serverService.getServerVersionAndCommit()
48 }
49
50 private getApproximateTime (seconds: number) {
51 const hours = Math.floor(seconds / 3600)
52 let pluralSuffix = ''
53 if (hours > 1) pluralSuffix = 's'
54 if (hours > 0) return `~ ${hours} hour${pluralSuffix}`
55
56 const minutes = Math.floor(seconds % 3600 / 60)
57
58 return this.i18n('~ {{minutes}} {minutes, plural, =1 {minute} other {minutes}}', { minutes })
59 }
60
61 private buildQuotaHelpIndication () {
62 if (this.initialUserVideoQuota === -1) return
63
64 const initialUserVideoQuotaBit = this.initialUserVideoQuota * 8
65
66 // 1080p: ~ 6Mbps
67 // 720p: ~ 4Mbps
68 // 360p: ~ 1.5Mbps
69 const fullHdSeconds = initialUserVideoQuotaBit / (6 * 1000 * 1000)
70 const hdSeconds = initialUserVideoQuotaBit / (4 * 1000 * 1000)
71 const normalSeconds = initialUserVideoQuotaBit / (1.5 * 1000 * 1000)
72
73 const lines = [
74 this.i18n('{{seconds}} of full HD videos', { seconds: this.getApproximateTime(fullHdSeconds) }),
75 this.i18n('{{seconds}} of HD videos', { seconds: this.getApproximateTime(hdSeconds) }),
76 this.i18n('{{seconds}} of average quality videos', { seconds: this.getApproximateTime(normalSeconds) })
77 ]
78
79 this.quotaHelpIndication = lines.join('<br />')
80 }
81}
diff --git a/client/src/app/shared/shared-instance/instance-follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts
new file mode 100644
index 000000000..3c9ccc40f
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-follow.service.ts
@@ -0,0 +1,116 @@
1import { SortMeta } from 'primeng/api'
2import { Observable } from 'rxjs'
3import { catchError, map } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core'
6import { RestExtractor, RestPagination, RestService } from '@app/core'
7import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index'
8import { environment } from '../../../environments/environment'
9
10@Injectable()
11export class InstanceFollowService {
12 private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restService: RestService,
17 private restExtractor: RestExtractor
18 ) {
19 }
20
21 getFollowing (options: {
22 pagination: RestPagination,
23 sort: SortMeta,
24 search?: string,
25 actorType?: ActivityPubActorType,
26 state?: FollowState
27 }): Observable<ResultList<ActorFollow>> {
28 const { pagination, sort, search, state, actorType } = options
29
30 let params = new HttpParams()
31 params = this.restService.addRestGetParams(params, pagination, sort)
32
33 if (search) params = params.append('search', search)
34 if (state) params = params.append('state', state)
35 if (actorType) params = params.append('actorType', actorType)
36
37 return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/following', { params })
38 .pipe(
39 map(res => this.restExtractor.convertResultListDateToHuman(res)),
40 catchError(res => this.restExtractor.handleError(res))
41 )
42 }
43
44 getFollowers (options: {
45 pagination: RestPagination,
46 sort: SortMeta,
47 search?: string,
48 actorType?: ActivityPubActorType,
49 state?: FollowState
50 }): Observable<ResultList<ActorFollow>> {
51 const { pagination, sort, search, state, actorType } = options
52
53 let params = new HttpParams()
54 params = this.restService.addRestGetParams(params, pagination, sort)
55
56 if (search) params = params.append('search', search)
57 if (state) params = params.append('state', state)
58 if (actorType) params = params.append('actorType', actorType)
59
60 return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/followers', { params })
61 .pipe(
62 map(res => this.restExtractor.convertResultListDateToHuman(res)),
63 catchError(res => this.restExtractor.handleError(res))
64 )
65 }
66
67 follow (notEmptyHosts: string[]) {
68 const body = {
69 hosts: notEmptyHosts
70 }
71
72 return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body)
73 .pipe(
74 map(this.restExtractor.extractDataBool),
75 catchError(res => this.restExtractor.handleError(res))
76 )
77 }
78
79 unfollow (follow: ActorFollow) {
80 return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host)
81 .pipe(
82 map(this.restExtractor.extractDataBool),
83 catchError(res => this.restExtractor.handleError(res))
84 )
85 }
86
87 acceptFollower (follow: ActorFollow) {
88 const handle = follow.follower.name + '@' + follow.follower.host
89
90 return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
91 .pipe(
92 map(this.restExtractor.extractDataBool),
93 catchError(res => this.restExtractor.handleError(res))
94 )
95 }
96
97 rejectFollower (follow: ActorFollow) {
98 const handle = follow.follower.name + '@' + follow.follower.host
99
100 return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
101 .pipe(
102 map(this.restExtractor.extractDataBool),
103 catchError(res => this.restExtractor.handleError(res))
104 )
105 }
106
107 removeFollower (follow: ActorFollow) {
108 const handle = follow.follower.name + '@' + follow.follower.host
109
110 return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
111 .pipe(
112 map(this.restExtractor.extractDataBool),
113 catchError(res => this.restExtractor.handleError(res))
114 )
115 }
116}
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.html b/client/src/app/shared/shared-instance/instance-statistics.component.html
new file mode 100644
index 000000000..399cf10fe
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.html
@@ -0,0 +1,101 @@
1<p i18n *ngIf="null === serverStats">Loading instance statistics...</p>
2
3<section *ngIf="null !== serverStats">
4 <h3 i18n>Local</h3>
5
6 <div class="row">
7 <div class="col-6 col-lg-4 col-xl-3">
8 <div class="card stat">
9 <div class="card-body">
10 <p class="stat-value">{{ serverStats.totalUsers }}</p>
11 <p class="stat-label" i18n>users</p>
12 </div>
13 <i class="glyphicon glyphicon-user icon-bottom"></i>
14 </div>
15 </div>
16
17 <div class="col-6 col-lg-4 col-xl-3">
18 <div class="card stat">
19 <div class="card-body">
20 <p class="stat-value">{{ serverStats.totalLocalVideos }}</p>
21 <p class="stat-label" i18n>videos</p>
22 </div>
23 <i class="glyphicon glyphicon-facetime-video"></i>
24 </div>
25 </div>
26
27 <div class="col-6 col-lg-4 col-xl-3">
28 <div class="card stat">
29 <div class="card-body">
30 <p class="stat-value">{{ serverStats.totalLocalVideoViews }}</p>
31 <p class="stat-label" i18n>video views</p>
32 </div>
33 <i class="glyphicon glyphicon-eye-open"></i>
34 </div>
35 </div>
36
37 <div class="col-6 col-lg-4 col-xl-3">
38 <div class="card stat">
39 <div class="card-body">
40 <p class="stat-value">{{ serverStats.totalLocalVideoComments }}</p>
41 <p class="stat-label" i18n>video comments</p>
42 </div>
43 <i class="glyphicon glyphicon-comment"></i>
44 </div>
45 </div>
46
47 <div class="col-6 col-lg-4 col-xl-3">
48 <div class="card stat">
49 <div class="card-body">
50 <p class="stat-value">{{ serverStats.totalLocalVideoFilesSize | bytes:1 }}</p>
51 <p class="stat-label" i18n>of hosted video</p>
52 </div>
53 <i class="glyphicon glyphicon-hdd"></i>
54 </div>
55 </div>
56 </div>
57
58 <h3 i18n>Federation</h3>
59
60 <div class="row">
61 <div class="col-6 col-lg-4 col-xl-3">
62 <div class="card stat">
63 <div class="card-body">
64 <p class="stat-value">{{ serverStats.totalVideos }}</p>
65 <p class="stat-label" i18n>videos</p>
66 </div>
67 <i class="glyphicon glyphicon-facetime-video"></i>
68 </div>
69 </div>
70
71 <div class="col-6 col-lg-4 col-xl-3">
72 <div class="card stat">
73 <div class="card-body">
74 <p class="stat-value">{{ serverStats.totalVideoComments }}</p>
75 <p class="stat-label" i18n>video comments</p>
76 </div>
77 <i class="glyphicon glyphicon-comment"></i>
78 </div>
79 </div>
80
81 <div class="col-6 col-lg-4 col-xl-3">
82 <div class="card stat">
83 <div class="card-body">
84 <p class="stat-value">{{ serverStats.totalInstanceFollowers }}</p>
85 <p class="stat-label" i18n>followers</p>
86 </div>
87 <i class="glyphicon glyphicon-retweet"></i>
88 </div>
89 </div>
90
91 <div class="col-6 col-lg-4 col-xl-3">
92 <div class="card stat">
93 <div class="card-body">
94 <p class="stat-value">{{ serverStats.totalInstanceFollowing }}</p>
95 <p class="stat-label" i18n>following</p>
96 </div>
97 <i class="glyphicon glyphicon-retweet"></i>
98 </div>
99 </div>
100 </div>
101</section>
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.scss b/client/src/app/shared/shared-instance/instance-statistics.component.scss
new file mode 100644
index 000000000..5286ab03a
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.scss
@@ -0,0 +1,40 @@
1
2h3 {
3 font-size: 1.25rem;
4}
5
6.stat {
7 text-align: center;
8 margin-bottom: 1em;
9 overflow: hidden;
10
11 .stat-value {
12 font-size: 2.25em;
13 line-height: 1em;
14 margin: 0;
15 }
16
17 .stat-label {
18 font-size: 1.15em;
19 margin: 0;
20 }
21
22 .glyphicon {
23 opacity: 0.12;
24 position: absolute;
25 left: 16px;
26 top: -24px;
27
28 &.icon-bottom {
29 top: 4px;
30 }
31
32 &::before {
33 font-size: 8em;
34 }
35 }
36
37 .card-body {
38 z-index: 2;
39 }
40}
diff --git a/client/src/app/shared/shared-instance/instance-statistics.component.ts b/client/src/app/shared/shared-instance/instance-statistics.component.ts
new file mode 100644
index 000000000..40aa8a4c0
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.ts
@@ -0,0 +1,22 @@
1import { Component, OnInit } from '@angular/core'
2import { ServerStats } from '@shared/models/server'
3import { ServerService } from '@app/core'
4
5@Component({
6 selector: 'my-instance-statistics',
7 templateUrl: './instance-statistics.component.html',
8 styleUrls: [ './instance-statistics.component.scss' ]
9})
10export class InstanceStatisticsComponent implements OnInit {
11 serverStats: ServerStats = null
12
13 constructor (
14 private serverService: ServerService
15 ) {
16 }
17
18 ngOnInit () {
19 this.serverService.getServerStats()
20 .subscribe(res => this.serverStats = res)
21 }
22}
diff --git a/client/src/app/shared/shared-instance/instance.service.ts b/client/src/app/shared/shared-instance/instance.service.ts
new file mode 100644
index 000000000..ba9797bb5
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance.service.ts
@@ -0,0 +1,88 @@
1import { forkJoin } from 'rxjs'
2import { catchError, map } from 'rxjs/operators'
3import { HttpClient } from '@angular/common/http'
4import { Injectable } from '@angular/core'
5import { MarkdownService, RestExtractor, ServerService } from '@app/core'
6import { About, peertubeTranslate } from '@shared/models'
7import { environment } from '../../../environments/environment'
8
9@Injectable()
10export class InstanceService {
11 private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config'
12 private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restExtractor: RestExtractor,
17 private markdownService: MarkdownService,
18 private serverService: ServerService
19 ) {
20 }
21
22 getAbout () {
23 return this.authHttp.get<About>(InstanceService.BASE_CONFIG_URL + '/about')
24 .pipe(catchError(res => this.restExtractor.handleError(res)))
25 }
26
27 contactAdministrator (fromEmail: string, fromName: string, subject: string, message: string) {
28 const body = {
29 fromEmail,
30 fromName,
31 subject,
32 body: message
33 }
34
35 return this.authHttp.post(InstanceService.BASE_SERVER_URL + '/contact', body)
36 .pipe(catchError(res => this.restExtractor.handleError(res)))
37
38 }
39
40 async buildHtml (about: About) {
41 const html = {
42 description: '',
43 terms: '',
44 codeOfConduct: '',
45 moderationInformation: '',
46 administrator: '',
47 hardwareInformation: ''
48 }
49
50 for (const key of Object.keys(html)) {
51 html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ])
52 }
53
54 return html
55 }
56
57 buildTranslatedLanguages (about: About) {
58 return forkJoin([
59 this.serverService.getVideoLanguages(),
60 this.serverService.getServerLocale()
61 ]).pipe(
62 map(([ languagesArray, translations ]) => {
63 return about.instance.languages
64 .map(l => {
65 const languageObj = languagesArray.find(la => la.id === l)
66
67 return peertubeTranslate(languageObj.label, translations)
68 })
69 })
70 )
71 }
72
73 buildTranslatedCategories (about: About) {
74 return forkJoin([
75 this.serverService.getVideoCategories(),
76 this.serverService.getServerLocale()
77 ]).pipe(
78 map(([ categoriesArray, translations ]) => {
79 return about.instance.categories
80 .map(c => {
81 const categoryObj = categoriesArray.find(ca => ca.id === c)
82
83 return peertubeTranslate(categoryObj.label, translations)
84 })
85 })
86 )
87 }
88}
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts
new file mode 100644
index 000000000..b75ad1a12
--- /dev/null
+++ b/client/src/app/shared/shared-instance/shared-instance.module.ts
@@ -0,0 +1,32 @@
1
2import { NgModule } from '@angular/core'
3import { SharedMainModule } from '../shared-main/shared-main.module'
4import { FeatureBooleanComponent } from './feature-boolean.component'
5import { InstanceFeaturesTableComponent } from './instance-features-table.component'
6import { InstanceFollowService } from './instance-follow.service'
7import { InstanceStatisticsComponent } from './instance-statistics.component'
8import { InstanceService } from './instance.service'
9
10@NgModule({
11 imports: [
12 SharedMainModule
13 ],
14
15 declarations: [
16 FeatureBooleanComponent,
17 InstanceFeaturesTableComponent,
18 InstanceStatisticsComponent
19 ],
20
21 exports: [
22 FeatureBooleanComponent,
23 InstanceFeaturesTableComponent,
24 InstanceStatisticsComponent
25 ],
26
27 providers: [
28 InstanceFollowService,
29 InstanceService
30 ]
31})
32export class SharedInstanceModule { }