app/page/statsShow.jsView |
---|
1 | 1 | const nest = require('depnest') |
2 | | -const { h, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, dictToCollection, throttle } = require('mutant') |
| 2 | +const { h, resolve, when, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, dictToCollection, throttle } = require('mutant') |
3 | 3 | const pull = require('pull-stream') |
4 | 4 | const marksum = require('markdown-summary') |
5 | 5 | const Chart = require('chart.js') |
6 | 6 | const groupBy = require('lodash/groupBy') |
| 7 | +const merge = require('lodash/merge') |
7 | 8 | |
8 | 9 | exports.gives = nest('app.page.statsShow') |
9 | 10 | |
10 | 11 | exports.needs = nest({ |
34 | 35 | lower: now - (howFarBack + 1) * THIRTY_DAYS |
35 | 36 | } |
36 | 37 | }) |
37 | 38 | |
38 | | - var rangeComments = computed([throttle(dictToCollection(store.comments), 1000), range], (comments, range) => { |
| 39 | + var commentsAll = computed(throttle(dictToCollection(store.comments), 1000), (comments) => { |
39 | 40 | return comments |
40 | 41 | .map(c => c.value) |
41 | 42 | .reduce((n, sofar) => [...n, ...sofar], []) |
| 43 | + }) |
| 44 | + |
| 45 | + |
| 46 | + var visibleComments = computed([commentsAll, range], (comments, range) => { |
| 47 | + return comments |
42 | 48 | .filter(msg => { |
43 | 49 | const ts = msg.value.timestamp |
44 | 50 | return ts >= range.lower && ts <= range.upper |
45 | 51 | }) |
48 | 54 | var rangeLikes = computed([throttle(dictToCollection(store.likes), 1000), range], (likes, range) => { |
49 | 55 | return likes |
50 | 56 | .map(c => c.value) |
51 | 57 | .reduce((n, sofar) => [...n, ...sofar], []) |
52 | | - .filter(msg => { |
53 | | - const ts = msg.value.timestamp |
54 | | - return ts >= range.lower && ts <= range.upper |
55 | | - }) |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + |
56 | 62 | }) |
57 | 63 | |
58 | 64 | onceTrue(api.sbot.obs.connection, server => { |
59 | 65 | fetchBlogs({ server, store }) |
60 | | - |
61 | 66 | |
62 | | - |
63 | | - |
64 | | - |
65 | | - |
66 | | - |
67 | | - |
68 | | - |
69 | | - |
70 | | - |
71 | | - |
72 | | - |
73 | | - |
74 | | - |
75 | | - |
76 | | - |
77 | | - |
78 | | - |
79 | | - |
80 | | - |
81 | | - |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | + |
82 | 77 | }) |
83 | 78 | const canvas = h('canvas', { height: 200, width: 600, style: { height: '200px', width: '600px' } }) |
84 | 79 | |
85 | 80 | const page = h('Page -statsShow', [ |
86 | | - h('div.content', [ |
87 | | - h('h1', 'Stats'), |
88 | | - h('section.totals', [ |
89 | | - h('div.comments', [ |
90 | | - h('div.count', computed(rangeComments, msgs => msgs.length)), |
91 | | - h('strong', 'Comments'), |
92 | | - '(30 days)' |
| 81 | + h('Scroller.content', [ |
| 82 | + h('div.content', [ |
| 83 | + h('h1', 'Stats'), |
| 84 | + h('section.totals', [ |
| 85 | + h('div.comments', [ |
| 86 | + h('div.count', computed(visibleComments, msgs => msgs.length)), |
| 87 | + h('strong', 'Comments'), |
| 88 | + '(30 days)' |
| 89 | + ]), |
| 90 | + h('div.likes', [ |
| 91 | + h('div.count', computed(rangeLikes, msgs => msgs.length)), |
| 92 | + h('strong', 'Likes'), |
| 93 | + '(30 days)' |
| 94 | + ]), |
| 95 | + h('div.shares', [ |
| 96 | + ]) |
93 | 97 | ]), |
94 | | - h('div.likes', [ |
95 | | - h('div.count', computed(rangeLikes, msgs => msgs.length)), |
96 | | - h('strong', 'Likes'), |
97 | | - '(30 days)' |
98 | | - ]), |
99 | | - h('div.shares', [ |
100 | | - ]) |
101 | | - ]), |
102 | | - h('section.graph', [ |
103 | | - canvas, |
104 | | - |
105 | | - h('div', [ |
106 | | - |
107 | | - |
108 | | - ]), |
109 | | - h('div.changeRange', [ |
110 | | - h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() + 1) }, '< Prev 30 days'), |
111 | | - ' | ', |
112 | | - h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() - 1) }, 'Next 30 days >') |
113 | | - ]) |
114 | | - ]), |
115 | | - h('table.blogs', [ |
116 | | - h('thead', [ |
117 | | - h('tr', [ |
118 | | - h('th.details'), |
119 | | - h('th.comment', 'Comments'), |
120 | | - h('th.likes', 'Likes') |
| 98 | + h('section.graph', [ |
| 99 | + canvas, |
| 100 | + h('div.changeRange', [ |
| 101 | + '< ', |
| 102 | + h('a', { 'ev-click': () => howFarBack.set(howFarBack() + 1) }, 'Prev 30 days'), |
| 103 | + ' | ', |
| 104 | + when(howFarBack, |
| 105 | + h('a', { 'ev-click': () => howFarBack.set(howFarBack() - 1) }, 'Next 30 days'), |
| 106 | + h('span', 'Next 30 days') |
| 107 | + ), |
| 108 | + ' >' |
121 | 109 | ]) |
122 | 110 | ]), |
123 | | - h('tbody', map(store.blogs, blog => h('tr.blog', { id: blog.key }, [ |
124 | | - h('td.details', [ |
125 | | - h('div.title', {}, getTitle(blog)), |
126 | | - h('a', |
127 | | - { |
128 | | - href: '#', |
129 | | - 'ev-click': viewBlog(blog) |
130 | | - }, |
131 | | - 'View blog' |
132 | | - ) |
| 111 | + h('table.blogs', [ |
| 112 | + h('thead', [ |
| 113 | + h('tr', [ |
| 114 | + h('th.details'), |
| 115 | + h('th.comment', 'Comments'), |
| 116 | + h('th.likes', 'Likes') |
| 117 | + ]) |
133 | 118 | ]), |
134 | | - h('td.comments', computed(store.comments.get(blog.key), msgs => msgs ? msgs.length : 0)), |
135 | | - h('td.likes', computed(store.likes.get(blog.key), msgs => msgs ? msgs.length : 0)) |
136 | | - |
137 | | - ]))) |
| 119 | + h('tbody', map(store.blogs, blog => h('tr.blog', { id: blog.key }, [ |
| 120 | + h('td.details', [ |
| 121 | + h('div.title', {}, getTitle(blog)), |
| 122 | + h('a', |
| 123 | + { |
| 124 | + href: '#', |
| 125 | + 'ev-click': viewBlog(blog) |
| 126 | + }, |
| 127 | + 'View blog' |
| 128 | + ) |
| 129 | + ]), |
| 130 | + h('td.comments', computed(store.comments.get(blog.key), msgs => msgs ? msgs.length : 0)), |
| 131 | + h('td.likes', computed(store.likes.get(blog.key), msgs => msgs ? msgs.length : 0)) |
| 132 | + |
| 133 | + ]))) |
| 134 | + ]) |
138 | 135 | ]) |
139 | 136 | ]) |
140 | 137 | ]) |
141 | 138 | |
142 | | - Chart.scaleService.updateScaleDefaults('linear', { |
143 | | - ticks: { min: 0 } |
144 | | - }) |
145 | | - var chart = new Chart(canvas.getContext('2d'), { |
146 | | - type: 'bar', |
147 | | - data: { |
148 | | - datasets: [{ |
149 | | - |
150 | | - backgroundColor: 'hsla(215, 57%, 60%, 1)', |
151 | | - borderColor: 'hsla(215, 57%, 60%, 1)', |
152 | | - |
153 | | - data: [ |
154 | | - ] |
155 | | - }] |
156 | | - }, |
157 | | - options: { |
158 | | - legend: { |
159 | | - display: false |
160 | | - }, |
161 | | - scales: { |
162 | | - xAxes: [{ |
163 | | - type: 'time', |
164 | | - distribution: 'linear', |
165 | | - time: { |
166 | | - unit: 'day', |
167 | | - min: new Date(range().lower), |
168 | | - max: new Date(range().upper) |
169 | | - }, |
170 | | - bounds: 'ticks', |
171 | | - ticks: { |
172 | | - maxTicksLimit: 4 |
173 | | - }, |
174 | | - gridLines: { |
175 | | - display: false |
176 | | - }, |
177 | | - maxBarThickness: 20 |
178 | | - }], |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | + var chart = new Chart(canvas.getContext('2d'), chartConfig({ range, chartData: [] })) |
179 | 143 | |
180 | | - yAxes: [{ |
181 | | - ticks: { |
182 | | - suggestedMin: 0, |
183 | | - suggestedMax: 10, |
184 | | - maxTicksLimit: 5 |
185 | | - } |
186 | | - }] |
187 | | - }, |
188 | | - animation: { |
189 | | - duration: 300 |
190 | | - } |
191 | | - } |
192 | | - }) |
| 144 | + const toDay = ts => Math.floor(ts / (24 * 60 * 60 * 1000)) |
193 | 145 | |
194 | | - const toDay = ts => Math.floor(ts / (24 * 60 * 60 * 1000)) |
195 | | - const rangeCommentData = computed(rangeComments, msgs => { |
| 146 | + |
| 147 | + const chartData = computed(commentsAll, msgs => { |
196 | 148 | const grouped = groupBy(msgs, m => toDay(m.value.timestamp)) |
197 | 149 | |
198 | 150 | var data = Object.keys(grouped) |
199 | 151 | .map(day => { |
203 | 155 | } |
204 | 156 | }) |
205 | 157 | return data |
206 | 158 | }) |
207 | | - rangeCommentData((newData) => { |
208 | | - chart.data.datasets[0].data = newData |
209 | 159 | |
210 | | - chart.options.scales.xAxes[0].time.min = new Date(range().lower) |
211 | | - chart.options.scales.xAxes[0].time.max = new Date(range().upper) |
| 160 | + chartData(() => { |
| 161 | + chart = merge(chart, chartConfig({ range, chartData })) |
| 162 | + chart.update() |
| 163 | + }) |
212 | 164 | |
| 165 | + range(() => { |
| 166 | + chart = merge(chart, chartConfig({ range, chartData })) |
213 | 167 | chart.update() |
214 | 168 | }) |
215 | 169 | |
216 | 170 | return page |
263 | 217 | |
264 | 218 | }) |
265 | 219 | ) |
266 | 220 | } |
| 221 | + |
| 222 | +function chartConfig ({ range, chartData }) { |
| 223 | + const { lower, upper } = resolve(range) |
| 224 | + |
| 225 | + const data = resolve(chartData) || [] |
| 226 | + const slice = data |
| 227 | + .filter(d => d.t >= lower && d.t <= upper) |
| 228 | + .map(d => d.y) |
| 229 | + .sort((a, b) => a < b) |
| 230 | + const localMax = slice[0] ? Math.max(slice[0], 10) : 10 |
| 231 | + |
| 232 | + return { |
| 233 | + type: 'bar', |
| 234 | + data: { |
| 235 | + datasets: [{ |
| 236 | + |
| 237 | + backgroundColor: 'hsla(215, 57%, 60%, 1)', |
| 238 | + borderColor: 'hsla(215, 57%, 60%, 1)', |
| 239 | + |
| 240 | + data |
| 241 | + }] |
| 242 | + }, |
| 243 | + options: { |
| 244 | + legend: { |
| 245 | + display: false |
| 246 | + }, |
| 247 | + scales: { |
| 248 | + xAxes: [{ |
| 249 | + type: 'time', |
| 250 | + distribution: 'linear', |
| 251 | + time: { |
| 252 | + unit: 'day', |
| 253 | + min: new Date(lower), |
| 254 | + max: new Date(upper), |
| 255 | + tooltipFormat: 'MMMM D', |
| 256 | + stepSize: 7 |
| 257 | + }, |
| 258 | + bounds: 'ticks', |
| 259 | + ticks: { |
| 260 | + |
| 261 | + }, |
| 262 | + gridLines: { |
| 263 | + display: false |
| 264 | + }, |
| 265 | + maxBarThickness: 20 |
| 266 | + }], |
| 267 | + |
| 268 | + yAxes: [{ |
| 269 | + ticks: { |
| 270 | + min: 0, |
| 271 | + max: Math.max(localMax, 10), |
| 272 | + stepSize: 5 |
| 273 | + } |
| 274 | + }] |
| 275 | + }, |
| 276 | + animation: { |
| 277 | + |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | +} |