diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-03-23 10:14:05 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-03-31 10:29:24 +0200 |
commit | 8165d00ac6263cf3c0d61d450960ef36635084ff (patch) | |
tree | c0587121cd8dbdfc246a5bc74c08805830140a77 /client | |
parent | 628c155338cf106365a06ca021b9f244b784c003 (diff) | |
download | PeerTube-8165d00ac6263cf3c0d61d450960ef36635084ff.tar.gz PeerTube-8165d00ac6263cf3c0d61d450960ef36635084ff.tar.zst PeerTube-8165d00ac6263cf3c0d61d450960ef36635084ff.zip |
View stats for channels
Diffstat (limited to 'client')
5 files changed, 101 insertions, 9 deletions
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index 2461aa3f5..94e74938b 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html | |||
@@ -6,7 +6,7 @@ | |||
6 | </div> | 6 | </div> |
7 | 7 | ||
8 | <div class="video-channels"> | 8 | <div class="video-channels"> |
9 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> | 9 | <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> |
10 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> | 10 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> |
11 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 11 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
12 | </a> | 12 | </a> |
@@ -17,13 +17,16 @@ | |||
17 | <div class="video-channel-name">{{ videoChannel.nameWithHost }}</div> | 17 | <div class="video-channel-name">{{ videoChannel.nameWithHost }}</div> |
18 | </a> | 18 | </a> |
19 | 19 | ||
20 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> | 20 | <div i18n class="video-channel-followers">{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> |
21 | |||
22 | <div *ngIf="!isInSmallView" class="w-100 d-flex justify-content-end"> | ||
23 | <p-chart *ngIf="videoChannelsData && videoChannelsData[i]" type="line" [data]="videoChannelsData[i]" [options]="chartOptions" width="40vw" height="100px"></p-chart> | ||
24 | </div> | ||
21 | </div> | 25 | </div> |
22 | 26 | ||
23 | <div class="video-channel-buttons"> | 27 | <div class="video-channel-buttons"> |
24 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> | ||
25 | |||
26 | <my-edit-button [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> | 28 | <my-edit-button [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> |
29 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> | ||
27 | </div> | 30 | </div> |
28 | </div> | 31 | </div> |
29 | </div> | 32 | </div> |
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 db0c7f94f..c0dc41f12 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 | |||
@@ -6,13 +6,14 @@ | |||
6 | } | 6 | } |
7 | 7 | ||
8 | ::ng-deep .action-button { | 8 | ::ng-deep .action-button { |
9 | &.action-button-delete { | 9 | &.action-button-edit { |
10 | margin-right: 10px; | 10 | margin-right: 10px; |
11 | } | 11 | } |
12 | } | 12 | } |
13 | 13 | ||
14 | .video-channel { | 14 | .video-channel { |
15 | @include row-blocks; | 15 | @include row-blocks; |
16 | padding-bottom: 0; | ||
16 | 17 | ||
17 | img { | 18 | img { |
18 | @include avatar(80px); | 19 | @include avatar(80px); |
@@ -58,6 +59,11 @@ | |||
58 | margin: 20px 0 50px; | 59 | margin: 20px 0 50px; |
59 | } | 60 | } |
60 | 61 | ||
62 | ::ng-deep .chartjs-render-monitor { | ||
63 | position: relative; | ||
64 | top: 1px; | ||
65 | } | ||
66 | |||
61 | @media screen and (max-width: $small-view) { | 67 | @media screen and (max-width: $small-view) { |
62 | .video-channels-header { | 68 | .video-channels-header { |
63 | text-align: center; | 69 | text-align: center; |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts index 3b01b6c9f..eeab3a8dd 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts | |||
@@ -4,9 +4,11 @@ import { AuthService } from '../../core/auth' | |||
4 | import { ConfirmService } from '../../core/confirm' | 4 | import { ConfirmService } from '../../core/confirm' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 6 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
7 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
7 | import { User } from '@app/shared' | 8 | import { User } from '@app/shared' |
8 | import { flatMap } from 'rxjs/operators' | 9 | import { flatMap } from 'rxjs/operators' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
11 | import { minBy, maxBy } from 'lodash-es' | ||
10 | 12 | ||
11 | @Component({ | 13 | @Component({ |
12 | selector: 'my-account-video-channels', | 14 | selector: 'my-account-video-channels', |
@@ -15,6 +17,9 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
15 | }) | 17 | }) |
16 | export class MyAccountVideoChannelsComponent implements OnInit { | 18 | export class MyAccountVideoChannelsComponent implements OnInit { |
17 | videoChannels: VideoChannel[] = [] | 19 | videoChannels: VideoChannel[] = [] |
20 | videoChannelsData: any[] | ||
21 | videoChannelsMinimumDailyViews = 0 | ||
22 | videoChannelsMaximumDailyViews: number | ||
18 | 23 | ||
19 | private user: User | 24 | private user: User |
20 | 25 | ||
@@ -23,6 +28,7 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
23 | private notifier: Notifier, | 28 | private notifier: Notifier, |
24 | private confirmService: ConfirmService, | 29 | private confirmService: ConfirmService, |
25 | private videoChannelService: VideoChannelService, | 30 | private videoChannelService: VideoChannelService, |
31 | private screenService: ScreenService, | ||
26 | private i18n: I18n | 32 | private i18n: I18n |
27 | ) {} | 33 | ) {} |
28 | 34 | ||
@@ -32,6 +38,61 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
32 | this.loadVideoChannels() | 38 | this.loadVideoChannels() |
33 | } | 39 | } |
34 | 40 | ||
41 | get isInSmallView () { | ||
42 | return this.screenService.isInSmallView() | ||
43 | } | ||
44 | |||
45 | get chartOptions () { | ||
46 | return { | ||
47 | legend: { | ||
48 | display: false | ||
49 | }, | ||
50 | scales: { | ||
51 | xAxes: [{ | ||
52 | display: false | ||
53 | }], | ||
54 | yAxes: [{ | ||
55 | display: false, | ||
56 | ticks: { | ||
57 | min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)), | ||
58 | max: this.videoChannelsMaximumDailyViews | ||
59 | } | ||
60 | }], | ||
61 | }, | ||
62 | layout: { | ||
63 | padding: { | ||
64 | left: 15, | ||
65 | right: 15, | ||
66 | top: 10, | ||
67 | bottom: 0 | ||
68 | } | ||
69 | }, | ||
70 | elements: { | ||
71 | point:{ | ||
72 | radius: 0 | ||
73 | } | ||
74 | }, | ||
75 | tooltips: { | ||
76 | mode: 'index', | ||
77 | intersect: false, | ||
78 | custom: function (tooltip: any) { | ||
79 | if (!tooltip) return; | ||
80 | // disable displaying the color box; | ||
81 | tooltip.displayColors = false; | ||
82 | }, | ||
83 | callbacks: { | ||
84 | label: function (tooltip: any, data: any) { | ||
85 | return `${tooltip.value} views`; | ||
86 | } | ||
87 | } | ||
88 | }, | ||
89 | hover: { | ||
90 | mode: 'index', | ||
91 | intersect: false | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
35 | async deleteVideoChannel (videoChannel: VideoChannel) { | 96 | async deleteVideoChannel (videoChannel: VideoChannel) { |
36 | const res = await this.confirmService.confirmWithInput( | 97 | const res = await this.confirmService.confirmWithInput( |
37 | this.i18n( | 98 | this.i18n( |
@@ -64,6 +125,21 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
64 | private loadVideoChannels () { | 125 | private loadVideoChannels () { |
65 | this.authService.userInformationLoaded | 126 | this.authService.userInformationLoaded |
66 | .pipe(flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account))) | 127 | .pipe(flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account))) |
67 | .subscribe(res => this.videoChannels = res.data) | 128 | .subscribe(res => { |
129 | this.videoChannels = res.data | ||
130 | this.videoChannelsData = this.videoChannels.map(v => ({ | ||
131 | labels: v.viewsPerDay.map(day => day.date.toLocaleDateString()), | ||
132 | datasets: [ | ||
133 | { | ||
134 | label: this.i18n('Views for the day'), | ||
135 | data: v.viewsPerDay.map(day => day.views), | ||
136 | fill: false, | ||
137 | borderColor: "#c6c6c6" | ||
138 | } | ||
139 | ] | ||
140 | })) | ||
141 | this.videoChannelsMinimumDailyViews = minBy(this.videoChannels.map(v => minBy(v.viewsPerDay, day => day.views)), day => day.views).views | ||
142 | this.videoChannelsMaximumDailyViews = maxBy(this.videoChannels.map(v => maxBy(v.viewsPerDay, day => day.views)), day => day.views).views | ||
143 | }) | ||
68 | } | 144 | } |
69 | } | 145 | } |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index f8c04cb4d..42b61bba6 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { TableModule } from 'primeng/table' | ||
2 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { TableModule } from 'primeng/table' | ||
3 | import { AutoCompleteModule } from 'primeng/autocomplete' | 3 | import { AutoCompleteModule } from 'primeng/autocomplete' |
4 | import { InputSwitchModule } from 'primeng/inputswitch' | 4 | import { InputSwitchModule } from 'primeng/inputswitch' |
5 | import { ChartModule } from 'primeng/chart' | ||
5 | import { SharedModule } from '../shared' | 6 | import { SharedModule } from '../shared' |
6 | import { MyAccountRoutingModule } from './my-account-routing.module' | 7 | import { MyAccountRoutingModule } from './my-account-routing.module' |
7 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' | 8 | import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component' |
@@ -44,7 +45,8 @@ import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-setti | |||
44 | SharedModule, | 45 | SharedModule, |
45 | TableModule, | 46 | TableModule, |
46 | InputSwitchModule, | 47 | InputSwitchModule, |
47 | DragDropModule | 48 | DragDropModule, |
49 | ChartModule | ||
48 | ], | 50 | ], |
49 | 51 | ||
50 | declarations: [ | 52 | declarations: [ |
diff --git a/client/src/app/shared/video-channel/video-channel.model.ts b/client/src/app/shared/video-channel/video-channel.model.ts index fec050cde..ee3288d7a 100644 --- a/client/src/app/shared/video-channel/video-channel.model.ts +++ b/client/src/app/shared/video-channel/video-channel.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { VideoChannel as ServerVideoChannel } from '../../../../../shared/models/videos' | 1 | import { VideoChannel as ServerVideoChannel, viewsPerTime } from '../../../../../shared/models/videos' |
2 | import { Actor } from '../actor/actor.model' | 2 | import { Actor } from '../actor/actor.model' |
3 | import { Account } from '../../../../../shared/models/actors' | 3 | import { Account } from '../../../../../shared/models/actors' |
4 | 4 | ||
@@ -12,6 +12,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
12 | ownerAccount?: Account | 12 | ownerAccount?: Account |
13 | ownerBy?: string | 13 | ownerBy?: string |
14 | ownerAvatarUrl?: string | 14 | ownerAvatarUrl?: string |
15 | viewsPerDay?: viewsPerTime[] | ||
15 | 16 | ||
16 | constructor (hash: ServerVideoChannel) { | 17 | constructor (hash: ServerVideoChannel) { |
17 | super(hash) | 18 | super(hash) |
@@ -23,6 +24,10 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
23 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) | 24 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) |
24 | this.nameWithHostForced = Actor.CREATE_BY_STRING(this.name, this.host, true) | 25 | this.nameWithHostForced = Actor.CREATE_BY_STRING(this.name, this.host, true) |
25 | 26 | ||
27 | if (hash.viewsPerDay) { | ||
28 | this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date)})) | ||
29 | } | ||
30 | |||
26 | if (hash.ownerAccount) { | 31 | if (hash.ownerAccount) { |
27 | this.ownerAccount = hash.ownerAccount | 32 | this.ownerAccount = hash.ownerAccount |
28 | this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) | 33 | this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) |