diff options
-rw-r--r-- | client/src/app/+my-library/+my-video-channels/my-video-channels.component.html | 2 | ||||
-rw-r--r-- | client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts | 132 |
2 files changed, 77 insertions, 57 deletions
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html index d97c35eff..5bef4a6ed 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html | |||
@@ -26,7 +26,7 @@ | |||
26 | 26 | ||
27 | <div class="no-results" i18n *ngIf="totalItems === 0">No channel found.</div> | 27 | <div class="no-results" i18n *ngIf="totalItems === 0">No channel found.</div> |
28 | 28 | ||
29 | <div class="video-channels"> | 29 | <div class="video-channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onChannelDataSubject.asObservable()"> |
30 | <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> | 30 | <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> |
31 | <my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> | 31 | <my-actor-avatar [actor]="videoChannel" actorType="channel" [internalHref]="[ '/c', videoChannel.nameWithHost ]" size="80"></my-actor-avatar> |
32 | 32 | ||
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts index ece59c2ff..633720a6c 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js' | 1 | import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js' |
2 | import { max, maxBy, min, minBy } from 'lodash-es' | 2 | import { max, maxBy, min, minBy } from 'lodash-es' |
3 | import { mergeMap } from 'rxjs/operators' | 3 | import { Subject } from 'rxjs' |
4 | import { Component } from '@angular/core' | 4 | import { Component } from '@angular/core' |
5 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' | 5 | import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, ScreenService } from '@app/core' |
6 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 6 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
@@ -15,13 +15,19 @@ export class MyVideoChannelsComponent { | |||
15 | videoChannels: VideoChannel[] = [] | 15 | videoChannels: VideoChannel[] = [] |
16 | 16 | ||
17 | videoChannelsChartData: ChartData[] | 17 | videoChannelsChartData: ChartData[] |
18 | videoChannelsMinimumDailyViews = 0 | ||
19 | videoChannelsMaximumDailyViews: number | ||
20 | 18 | ||
21 | chartOptions: ChartOptions | 19 | chartOptions: ChartOptions |
22 | 20 | ||
23 | search: string | 21 | search: string |
24 | 22 | ||
23 | onChannelDataSubject = new Subject<any>() | ||
24 | |||
25 | pagination: ComponentPagination = { | ||
26 | currentPage: 1, | ||
27 | itemsPerPage: 10, | ||
28 | totalItems: null | ||
29 | } | ||
30 | |||
25 | constructor ( | 31 | constructor ( |
26 | private authService: AuthService, | 32 | private authService: AuthService, |
27 | private notifier: Notifier, | 33 | private notifier: Notifier, |
@@ -36,7 +42,12 @@ export class MyVideoChannelsComponent { | |||
36 | 42 | ||
37 | onSearch (search: string) { | 43 | onSearch (search: string) { |
38 | this.search = search | 44 | this.search = search |
39 | this.loadVideoChannels() | 45 | |
46 | this.pagination.currentPage = 1 | ||
47 | this.videoChannels = [] | ||
48 | |||
49 | this.authService.userInformationLoaded | ||
50 | .subscribe(() => this.loadMoreVideoChannels()) | ||
40 | } | 51 | } |
41 | 52 | ||
42 | async deleteVideoChannel (videoChannel: VideoChannel) { | 53 | async deleteVideoChannel (videoChannel: VideoChannel) { |
@@ -56,7 +67,7 @@ channel with the same name (${videoChannel.name})!`, | |||
56 | this.videoChannelService.removeVideoChannel(videoChannel) | 67 | this.videoChannelService.removeVideoChannel(videoChannel) |
57 | .subscribe({ | 68 | .subscribe({ |
58 | next: () => { | 69 | next: () => { |
59 | this.loadVideoChannels() | 70 | this.videoChannels = this.videoChannels.filter(c => c.id !== videoChannel.id) |
60 | this.notifier.success($localize`Video channel ${videoChannel.displayName} deleted.`) | 71 | this.notifier.success($localize`Video channel ${videoChannel.displayName} deleted.`) |
61 | }, | 72 | }, |
62 | 73 | ||
@@ -64,58 +75,67 @@ channel with the same name (${videoChannel.name})!`, | |||
64 | }) | 75 | }) |
65 | } | 76 | } |
66 | 77 | ||
67 | private loadVideoChannels () { | 78 | onNearOfBottom () { |
68 | this.authService.userInformationLoaded | 79 | if (!hasMoreItems(this.pagination)) return |
69 | .pipe(mergeMap(() => { | 80 | |
70 | const user = this.authService.getUser() | 81 | this.pagination.currentPage += 1 |
71 | const options = { | ||
72 | account: user.account, | ||
73 | withStats: true, | ||
74 | search: this.search, | ||
75 | sort: '-updatedAt' | ||
76 | } | ||
77 | 82 | ||
78 | return this.videoChannelService.listAccountVideoChannels(options) | 83 | this.loadMoreVideoChannels() |
79 | })).subscribe(res => { | 84 | } |
80 | this.videoChannels = res.data | 85 | |
81 | this.totalItems = res.total | 86 | private loadMoreVideoChannels () { |
82 | 87 | const user = this.authService.getUser() | |
83 | // chart data | 88 | const options = { |
84 | this.videoChannelsChartData = this.videoChannels.map(v => ({ | 89 | account: user.account, |
85 | labels: v.viewsPerDay.map(day => day.date.toLocaleDateString()), | 90 | withStats: true, |
86 | datasets: [ | 91 | search: this.search, |
87 | { | 92 | componentPagination: this.pagination, |
88 | label: $localize`Views for the day`, | 93 | sort: '-updatedAt' |
89 | data: v.viewsPerDay.map(day => day.views), | 94 | } |
90 | fill: false, | 95 | |
91 | borderColor: '#c6c6c6' | 96 | return this.videoChannelService.listAccountVideoChannels(options) |
92 | } | 97 | .subscribe(res => { |
93 | ] | 98 | this.videoChannels = this.videoChannels.concat(res.data) |
94 | } as ChartData)) | 99 | this.totalItems = res.total |
95 | 100 | ||
96 | // chart options that depend on chart data: | 101 | // chart data |
97 | // we don't want to skew values and have min at 0, so we define what the floor/ceiling is here | 102 | this.videoChannelsChartData = this.videoChannels.map(v => ({ |
98 | this.videoChannelsMinimumDailyViews = min( | 103 | labels: v.viewsPerDay.map(day => day.date.toLocaleDateString()), |
99 | // compute local minimum daily views for each channel, by their "views" attribute | 104 | datasets: [ |
100 | this.videoChannels.map(v => minBy( | 105 | { |
101 | v.viewsPerDay, | 106 | label: $localize`Views for the day`, |
102 | day => day.views | 107 | data: v.viewsPerDay.map(day => day.views), |
103 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | 108 | fill: false, |
104 | ) | 109 | borderColor: '#c6c6c6' |
105 | 110 | } | |
106 | this.videoChannelsMaximumDailyViews = max( | 111 | ] |
107 | // compute local maximum daily views for each channel, by their "views" attribute | 112 | } as ChartData)) |
108 | this.videoChannels.map(v => maxBy( | 113 | |
109 | v.viewsPerDay, | 114 | this.buildChartOptions() |
110 | day => day.views | 115 | |
111 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | 116 | this.onChannelDataSubject.next(res.data) |
112 | ) | 117 | }) |
113 | |||
114 | this.buildChartOptions() | ||
115 | }) | ||
116 | } | 118 | } |
117 | 119 | ||
118 | private buildChartOptions () { | 120 | private buildChartOptions () { |
121 | // chart options that depend on chart data: | ||
122 | // we don't want to skew values and have min at 0, so we define what the floor/ceiling is here | ||
123 | const videoChannelsMinimumDailyViews = min( | ||
124 | // compute local minimum daily views for each channel, by their "views" attribute | ||
125 | this.videoChannels.map(v => minBy( | ||
126 | v.viewsPerDay, | ||
127 | day => day.views | ||
128 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | ||
129 | ) | ||
130 | |||
131 | const videoChannelsMaximumDailyViews = max( | ||
132 | // compute local maximum daily views for each channel, by their "views" attribute | ||
133 | this.videoChannels.map(v => maxBy( | ||
134 | v.viewsPerDay, | ||
135 | day => day.views | ||
136 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | ||
137 | ) | ||
138 | |||
119 | this.chartOptions = { | 139 | this.chartOptions = { |
120 | plugins: { | 140 | plugins: { |
121 | legend: { | 141 | legend: { |
@@ -141,8 +161,8 @@ channel with the same name (${videoChannel.name})!`, | |||
141 | }, | 161 | }, |
142 | y: { | 162 | y: { |
143 | display: false, | 163 | display: false, |
144 | min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)), | 164 | min: Math.max(0, videoChannelsMinimumDailyViews - (3 * videoChannelsMaximumDailyViews / 100)), |
145 | max: Math.max(1, this.videoChannelsMaximumDailyViews) | 165 | max: Math.max(1, videoChannelsMaximumDailyViews) |
146 | } | 166 | } |
147 | }, | 167 | }, |
148 | layout: { | 168 | layout: { |