]>
Commit | Line | Data |
---|---|---|
1 | import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js' | |
2 | import { max, maxBy, min, minBy } from 'lodash-es' | |
3 | import { mergeMap } from 'rxjs/operators' | |
4 | import { Component } from '@angular/core' | |
5 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' | |
6 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' | |
7 | ||
8 | @Component({ | |
9 | templateUrl: './my-video-channels.component.html', | |
10 | styleUrls: [ './my-video-channels.component.scss' ] | |
11 | }) | |
12 | export class MyVideoChannelsComponent { | |
13 | totalItems: number | |
14 | ||
15 | videoChannels: VideoChannel[] = [] | |
16 | ||
17 | videoChannelsChartData: ChartData[] | |
18 | videoChannelsMinimumDailyViews = 0 | |
19 | videoChannelsMaximumDailyViews: number | |
20 | ||
21 | chartOptions: ChartOptions | |
22 | ||
23 | search: string | |
24 | ||
25 | constructor ( | |
26 | private authService: AuthService, | |
27 | private notifier: Notifier, | |
28 | private confirmService: ConfirmService, | |
29 | private videoChannelService: VideoChannelService, | |
30 | private screenService: ScreenService | |
31 | ) {} | |
32 | ||
33 | get isInSmallView () { | |
34 | return this.screenService.isInSmallView() | |
35 | } | |
36 | ||
37 | onSearch (search: string) { | |
38 | this.search = search | |
39 | this.loadVideoChannels() | |
40 | } | |
41 | ||
42 | async deleteVideoChannel (videoChannel: VideoChannel) { | |
43 | const res = await this.confirmService.confirmWithInput( | |
44 | $localize`Do you really want to delete ${videoChannel.displayName}? | |
45 | It will delete ${videoChannel.videosCount} videos uploaded in this channel, and you will not be able to create another | |
46 | channel with the same name (${videoChannel.name})!`, | |
47 | ||
48 | $localize`Please type the name of the video channel (${videoChannel.name}) to confirm`, | |
49 | ||
50 | videoChannel.name, | |
51 | ||
52 | $localize`Delete` | |
53 | ) | |
54 | if (res === false) return | |
55 | ||
56 | this.videoChannelService.removeVideoChannel(videoChannel) | |
57 | .subscribe({ | |
58 | next: () => { | |
59 | this.loadVideoChannels() | |
60 | this.notifier.success($localize`Video channel ${videoChannel.displayName} deleted.`) | |
61 | }, | |
62 | ||
63 | error: err => this.notifier.error(err.message) | |
64 | }) | |
65 | } | |
66 | ||
67 | private loadVideoChannels () { | |
68 | this.authService.userInformationLoaded | |
69 | .pipe(mergeMap(() => { | |
70 | const user = this.authService.getUser() | |
71 | const options = { | |
72 | account: user.account, | |
73 | withStats: true, | |
74 | search: this.search, | |
75 | sort: '-updatedAt' | |
76 | } | |
77 | ||
78 | return this.videoChannelService.listAccountVideoChannels(options) | |
79 | })).subscribe(res => { | |
80 | this.videoChannels = res.data | |
81 | this.totalItems = res.total | |
82 | ||
83 | // chart data | |
84 | this.videoChannelsChartData = this.videoChannels.map(v => ({ | |
85 | labels: v.viewsPerDay.map(day => day.date.toLocaleDateString()), | |
86 | datasets: [ | |
87 | { | |
88 | label: $localize`Views for the day`, | |
89 | data: v.viewsPerDay.map(day => day.views), | |
90 | fill: false, | |
91 | borderColor: '#c6c6c6' | |
92 | } | |
93 | ] | |
94 | } as ChartData)) | |
95 | ||
96 | // chart options that depend on chart data: | |
97 | // we don't want to skew values and have min at 0, so we define what the floor/ceiling is here | |
98 | this.videoChannelsMinimumDailyViews = min( | |
99 | // compute local minimum daily views for each channel, by their "views" attribute | |
100 | this.videoChannels.map(v => minBy( | |
101 | v.viewsPerDay, | |
102 | day => day.views | |
103 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | |
104 | ) | |
105 | ||
106 | this.videoChannelsMaximumDailyViews = max( | |
107 | // compute local maximum daily views for each channel, by their "views" attribute | |
108 | this.videoChannels.map(v => maxBy( | |
109 | v.viewsPerDay, | |
110 | day => day.views | |
111 | ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute | |
112 | ) | |
113 | ||
114 | this.buildChartOptions() | |
115 | }) | |
116 | } | |
117 | ||
118 | private buildChartOptions () { | |
119 | this.chartOptions = { | |
120 | plugins: { | |
121 | legend: { | |
122 | display: false | |
123 | }, | |
124 | tooltip: { | |
125 | mode: 'index', | |
126 | intersect: false, | |
127 | external: function ({ tooltip }: { tooltip: TooltipModel<any> }) { | |
128 | if (!tooltip) return | |
129 | ||
130 | // disable displaying the color box | |
131 | tooltip.options.displayColors = false | |
132 | }, | |
133 | callbacks: { | |
134 | label: (tooltip: TooltipItem<any>) => `${tooltip.formattedValue} views` | |
135 | } | |
136 | } | |
137 | }, | |
138 | scales: { | |
139 | x: { | |
140 | display: false | |
141 | }, | |
142 | y: { | |
143 | display: false, | |
144 | min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)), | |
145 | max: Math.max(1, this.videoChannelsMaximumDailyViews) | |
146 | } | |
147 | }, | |
148 | layout: { | |
149 | padding: { | |
150 | left: 15, | |
151 | right: 15, | |
152 | top: 10, | |
153 | bottom: 0 | |
154 | } | |
155 | }, | |
156 | elements: { | |
157 | point: { | |
158 | radius: 0 | |
159 | } | |
160 | }, | |
161 | hover: { | |
162 | mode: 'index', | |
163 | intersect: false | |
164 | } | |
165 | } | |
166 | } | |
167 | } |