app/page/statsShow.jsView |
---|
1 | 1 | const nest = require('depnest') |
2 | | -const { h, Array: MutantArray, Dict, onceTrue, map } = require('mutant') |
| 2 | +const { h, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, dictToCollection } = require('mutant') |
3 | 3 | const pull = require('pull-stream') |
4 | 4 | |
5 | 5 | exports.gives = nest('app.page.statsShow') |
6 | 6 | |
7 | 7 | exports.needs = nest({ |
8 | | - 'sbot.obs.connection': 'first' |
| 8 | + 'sbot.obs.connection': 'first', |
| 9 | + 'history.sync.push': 'first' |
9 | 10 | }) |
10 | 11 | |
11 | 12 | exports.create = (api) => { |
12 | 13 | return nest('app.page.statsShow', statsShow) |
13 | 14 | |
14 | 15 | function statsShow (location) { |
15 | | - var blogs = MutantArray([]) |
16 | | - var comments = Dict() |
17 | | - var likes = Dict() |
18 | | - |
| 16 | + var store = Struct({ |
| 17 | + blogs: MutantArray([]), |
| 18 | + comments: Dict(), |
| 19 | + likes: Dict() |
| 20 | + }) |
19 | 21 | |
20 | | - onceTrue(api.sbot.obs.connection, server => { |
21 | | - |
| 22 | + var howFarBack = Value(0) |
| 23 | + |
| 24 | + const now = Date.now() |
| 25 | + const thirtyDays = 30 * 24 * 60 * 60 * 1000 |
22 | 26 | |
23 | | - fetchBlogs() |
24 | | - |
25 | | - function fetchBlogs () { |
26 | | - pull( |
27 | | - server.blogStats.readBlogs({ reverse: false }), |
28 | | - pull.drain(blog => { |
29 | | - blogs.push(blog) |
30 | | - fetchComments(blog) |
31 | | - fetchLikes(blog) |
32 | | - }) |
33 | | - ) |
| 27 | + |
| 28 | + var range = computed([howFarBack], howFarBack => { |
| 29 | + return { |
| 30 | + upper: now - howFarBack * thirtyDays, |
| 31 | + lower: now - (howFarBack + 1) * thirtyDays |
34 | 32 | } |
| 33 | + }) |
35 | 34 | |
36 | | - function fetchComments (blog) { |
37 | | - if (!comments.has(blog.key)) comments.put(blog.key, MutantArray()) |
| 35 | + var rangeComments = computed([dictToCollection(store.comments), range], (comments, range) => { |
| 36 | + return comments |
| 37 | + .map(c => c.value) |
| 38 | + .reduce((n, sofar) => [...n, ...sofar], []) |
| 39 | + .filter(msg => { |
| 40 | + const ts = msg.value.timestamp |
| 41 | + return ts >= range.lower && ts <= range.upper |
| 42 | + }) |
| 43 | + }) |
38 | 44 | |
39 | | - pull( |
40 | | - server.blogStats.readComments({ blog }), |
41 | | - pull.drain(comment => { |
42 | | - comments.get(blog.key).push(comment) |
43 | | - }) |
44 | | - ) |
45 | | - } |
| 45 | + var rangeLikes = computed([dictToCollection(store.likes), range], (likes, range) => { |
| 46 | + return likes |
| 47 | + .map(c => c.value) |
| 48 | + .reduce((n, sofar) => [...n, ...sofar], []) |
| 49 | + .filter(msg => { |
| 50 | + const ts = msg.value.timestamp |
| 51 | + return ts >= range.lower && ts <= range.upper |
| 52 | + }) |
| 53 | + }) |
46 | 54 | |
47 | | - function fetchLikes (blog) { |
48 | | - if (!likes.has(blog.key)) likes.put(blog.key, MutantArray()) |
| 55 | + onceTrue(api.sbot.obs.connection, server => { |
| 56 | + fetchBlogs({ server, store }) |
| 57 | + |
49 | 58 | |
50 | | - pull( |
51 | | - server.blogStats.readLikes({ blog }), |
52 | | - pull.drain(comment => { |
53 | | - likes.get(blog.key).push(comment) |
54 | | - }) |
55 | | - ) |
56 | | - } |
57 | | - |
58 | 59 | |
59 | | - var blogKey = '%3JeEg7voZF4aplk9xCEAfhFOx+zocbKhgstzvfD3G8w=.sha256' |
| 60 | + |
60 | 61 | |
61 | 62 | |
62 | | - pull( |
63 | | - server.blogStats.read({ |
64 | | - gt: ['L', blogKey, null], |
65 | | - lt: ['L', blogKey+'~', undefined], |
66 | | - |
67 | | - |
68 | | - |
69 | | - keys: true, |
70 | | - values: true, |
71 | | - seqs: false, |
72 | | - reverse: true |
73 | | - }), |
74 | | - |
75 | | - pull.log(() => console.log('DONE')) |
76 | | - ) |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | + |
| 77 | + |
77 | 78 | |
78 | 79 | }) |
79 | 80 | |
80 | 81 | return h('Page -statsShow', [ |
81 | | - h('pre', map(blogs, blog => { |
82 | | - return h('div', [ |
83 | | - h('b', blog.value.content.title), |
84 | | - h('div', map(comments.get(blog.key), msg => 'C')), |
85 | | - h('div', map(likes.get(blog.key), msg => 'L')) |
| 82 | + h('div.content', [ |
| 83 | + h('h1', 'Stats'), |
| 84 | + h('section.totals', [ |
| 85 | + h('div.comments', [ |
| 86 | + h('div.count', computed(rangeComments, 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 | + ]) |
| 97 | + ]), |
| 98 | + h('section.graph', [ |
| 99 | + |
| 100 | + h('div', [ |
| 101 | + h('div', [ 'Comments ', map(rangeComments, msg => [new Date(msg.value.timestamp).toDateString(), ' ']) ]), |
| 102 | + h('div', [ 'Likes ', map(rangeLikes, msg => [new Date(msg.value.timestamp).toDateString(), ' ']) ]) |
| 103 | + ]), |
| 104 | + h('div', [ |
| 105 | + h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() + 1) }, '< Prev 30 days'), |
| 106 | + ' | ', |
| 107 | + h('a', { href: '#', 'ev-click': () => howFarBack.set(howFarBack() - 1) }, 'Next 30 days >'), |
| 108 | + h('div', ['howFarBack:', howFarBack]) |
| 109 | + ]) |
| 110 | + ]), |
| 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 | + ]) |
| 118 | + ]), |
| 119 | + h('tbody', map(store.blogs, blog => h('tr.blog', [ |
| 120 | + h('td.details', [ |
| 121 | + h('div.title', {}, blog.value.content.title), |
| 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 | + ]))) |
86 | 133 | ]) |
87 | | - })) |
| 134 | + ]) |
88 | 135 | ]) |
89 | 136 | } |
| 137 | + |
| 138 | + function viewBlog (blog) { |
| 139 | + return () => api.history.sync.push(blog) |
| 140 | + } |
90 | 141 | } |
| 142 | + |
| 143 | +function fetchBlogs ({ server, store }) { |
| 144 | + pull( |
| 145 | + server.blogStats.readBlogs({ reverse: false }), |
| 146 | + pull.drain(blog => { |
| 147 | + store.blogs.push(blog) |
| 148 | + |
| 149 | + fetchComments({ server, store, blog }) |
| 150 | + fetchLikes({ server, store, blog }) |
| 151 | + }) |
| 152 | + ) |
| 153 | +} |
| 154 | + |
| 155 | +function fetchComments ({ server, store, blog }) { |
| 156 | + if (!store.comments.has(blog.key)) store.comments.put(blog.key, MutantArray()) |
| 157 | + |
| 158 | + pull( |
| 159 | + server.blogStats.readComments(blog), |
| 160 | + pull.drain(comment => { |
| 161 | + store.comments.get(blog.key).push(comment) |
| 162 | + |
| 163 | + }) |
| 164 | + ) |
| 165 | +} |
| 166 | + |
| 167 | +function fetchLikes ({ server, store, blog }) { |
| 168 | + if (!store.likes.has(blog.key)) store.likes.put(blog.key, MutantArray()) |
| 169 | + |
| 170 | + pull( |
| 171 | + server.blogStats.readLikes(blog), |
| 172 | + pull.drain(comment => { |
| 173 | + store.likes.get(blog.key).push(comment) |
| 174 | + |
| 175 | + |
| 176 | + |
| 177 | + }) |
| 178 | + ) |
| 179 | +} |