git ssb

2+

mixmix / ticktack



Commit cc9b1a7a5f4f79d8e0f7c4a2491a50f440ac9153

Dry up stats code, fix md titles of Plogs

mix irving committed on 4/25/2018, 11:41:55 PM
Parent: 157f35fd4dc9f9c94e9aec261e4b634b9f525e11

Files changed

app/page/statsShow.jschanged
app/page/statsShow.mcsschanged
ssb-server-blog-stats.jschanged
app/page/statsShow.jsView
@@ -1,22 +1,23 @@
11 const nest = require('depnest')
2-const { h, resolve, when, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, dictToCollection, throttle, watchAll } = require('mutant')
2+const { h, resolve, when, Value, Struct, Array: MutantArray, Dict, onceTrue, map, computed, throttle, watchAll } = require('mutant')
33 const pull = require('pull-stream')
44 const marksum = require('markdown-summary')
55 const Chart = require('chart.js')
66 const groupBy = require('lodash/groupBy')
7-const mergeWith = require('lodash/mergeWith')
87 const flatMap = require('lodash/flatMap')
98
109 exports.gives = nest('app.page.statsShow')
1110
1211 exports.needs = nest({
1312 'sbot.obs.connection': 'first',
14- 'history.sync.push': 'first'
13+ 'history.sync.push': 'first',
14+ 'message.html.markdown': 'first'
1515 })
1616
1717 const COMMENTS = 'comments'
1818 const LIKES = 'likes'
19+const SHARES = 'shares'
1920 const DAY = 24 * 60 * 60 * 1000
2021
2122 exports.create = (api) => {
2223 return nest('app.page.statsShow', statsShow)
@@ -24,12 +25,23 @@
2425 function statsShow (location) {
2526 var store = Struct({
2627 blogs: MutantArray([]),
2728 comments: Dict(),
28- likes: Dict()
29+ likes: Dict(),
30+ shares: Dict()
2931 })
30- onceTrue(api.sbot.obs.connection, server => fetchBlogs({ server, store }))
32+ onceTrue(api.sbot.obs.connection, server => fetchBlogData({ server, store }))
3133
34+ var foci = Struct({
35+ [COMMENTS]: computed([throttle(store.comments, 1000)], (msgs) => {
36+ return flatMap(msgs, (val, key) => val)
37+ }),
38+ [LIKES]: computed([throttle(store.likes, 1000)], (msgs) => {
39+ return flatMap(msgs, (val, key) => val)
40+ }),
41+ [SHARES]: []
42+ })
43+
3244 var howFarBack = Value(0)
3345 // stats show a moving window of 30 days
3446 var context = Struct({
3547 focus: Value(COMMENTS),
@@ -42,80 +54,37 @@
4254 lower: endOfDay - (howFarBack + 1) * 30 * DAY
4355 }
4456 })
4557 })
46- console.log(context.range())
47- context.range(console.log)
4858
49- var foci = Struct({
50- [COMMENTS]: computed([throttle(store.comments, 1000)], (msgs) => {
51- return flatMap(msgs, (val, key) => val)
52- }),
53- [LIKES]: computed([throttle(store.likes, 1000)], (msgs) => {
54- return flatMap(msgs, (val, key) => val)
59+ function totalOnscreenData (focus) {
60+ return computed([foci[focus], context.range], (msgs, range) => {
61+ return msgs
62+ .filter(msg => {
63+ const ts = msg.value.timestamp
64+ return ts > range.lower && ts <= range.upper
65+ })
66+ .length
5567 })
68+ }
5669
57- })
58-
59- var visibleCommentsCount = computed([foci.comments, context.range], (msgs, range) => {
60- return msgs
61- .filter(msg => {
62- const ts = msg.value.timestamp
63- return ts > range.lower && ts <= range.upper
64- })
65- .length
66- })
67-
68- var visibleLikesCount = computed([foci.likes, context.range], (msgs, range) => {
69- return msgs
70- .filter(msg => {
71- const ts = msg.value.timestamp
72- return ts > range.lower && ts <= range.upper
73- })
74- .length
75- })
76-
7770 const canvas = h('canvas', { height: 200, width: 600, style: { height: '200px', width: '600px' } })
7871
79- const displayComments = () => context.focus.set(COMMENTS)
80- const displayLikes = () => context.focus.set(LIKES)
81-
8272 const page = h('Page -statsShow', [
8373 h('Scroller.content', [
8474 h('div.content', [
8575 h('h1', 'Stats'),
86- h('section.totals', [
87- h('div.comments',
76+ h('section.totals', [COMMENTS, LIKES, SHARES].map(focus => {
77+ return h('div',
8878 {
89- className: computed(context.focus, focus => focus === COMMENTS ? '-selected' : ''),
90- 'ev-click': displayComments
79+ classList: computed(context.focus, f => f === focus ? [focus, '-selected'] : [focus]),
80+ 'ev-click': () => context.focus.set(focus)
9181 }, [
92- h('div.count', visibleCommentsCount),
93- h('strong', 'Comments'),
82+ h('div.count', totalOnscreenData(focus)),
83+ h('strong', focus),
9484 '(30 days)'
95- ]),
96- h('div.likes',
97- {
98- className: computed(context.focus, focus => focus === LIKES ? '-selected' : ''),
99- 'ev-click': displayLikes
100- }, [
101- h('div.count', visibleLikesCount),
102- h('strong', 'Likes'),
103- '(30 days)'
104- ]
105- ),
106- h('div.shares',
107- {
108- className: when(context.shares, '-selected')
109- // 'ev-click': displayShares
110- }, [
111- // h('div.count', computed(rangeLikes, msgs => msgs.length)),
112- h('div.count', '--'),
113- h('strong', 'Shares'),
114- '(30 days)'
115- ]
116- )
117- ]),
85+ ])
86+ })),
11887 h('section.graph', [
11988 canvas,
12089 h('div.changeRange', [
12190 '< ',
@@ -131,109 +100,57 @@
131100 h('table.blogs', [
132101 h('thead', [
133102 h('tr', [
134103 h('th.details'),
135- h('th.comment', 'Comments'),
136- h('th.likes', 'Likes')
104+ h('th.comments', 'Comments'),
105+ h('th.likes', 'Likes'),
106+ h('th.shares', 'Shares')
137107 ])
138108 ]),
139- h('tbody', map(store.blogs, blog => h('tr.blog', { id: blog.key }, [
140- h('td.details', [
141- h('div.title', {}, getTitle(blog)),
142- h('a',
143- {
144- href: '#',
145- 'ev-click': viewBlog(blog)
146- },
147- 'View blog'
148- )
149- ]),
150- h('td.comments', computed(store.comments.get(blog.key), msgs => msgs ? msgs.length : 0)),
151- h('td.likes', computed(store.likes.get(blog.key), msgs => msgs ? msgs.length : 0))
152- // ]), { comparer: (a, b) => a === b }))
153- ])))
109+ h('tbody', map(store.blogs, BlogRow))
154110 ])
155111 ])
156112 ])
157113 ])
158114
159- var chart = new Chart(canvas.getContext('2d'), chartConfig({ context, chartData: [] }))
160-
161- // HACK - if the focus has changed, then zero the data
162- // this prevents the graph from showing some confusing animations when transforming between foci
163- var lastFocus = context.focus()
164- const zeroGraphOnFocusChange = (focus) => {
165- if (focus !== lastFocus) {
166- chart.data.datasets[0].data = []
167- chart.update()
168- lastFocus = focus
169- }
115+ function BlogRow (blog) {
116+ return h('tr.blog', { id: blog.key }, [
117+ h('td.details', [
118+ h('div.title', {}, getTitle({ blog, mdRenderer: api.message.html.markdown })),
119+ h('a',
120+ {
121+ href: '#',
122+ 'ev-click': ev => {
123+ ev.stopPropagation() // stop the click catcher!
124+ api.history.sync.push(blog)
125+ }
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+ h('td.shares', computed(store.shares.get(blog.key), msgs => msgs ? msgs.length : 0))
133+ ])
170134 }
171- const toDay = ts => Math.floor(ts / DAY)
172- const chartData = computed([context.focus, foci], (focus, foci) => {
173- zeroGraphOnFocusChange(focus)
174- const msgs = foci[focus]
175- const grouped = groupBy(msgs, m => toDay(m.value.timestamp))
176135
177- var data = Object.keys(grouped)
178- .map(day => {
179- return {
180- t: day * DAY,
181- y: grouped[day].length
182- }
183- })
184- return data
185- })
136+ initialiseChart({ canvas, context, foci })
186137
187- chartData(data => {
188- chart.data.datasets[0].data = data
189-
190- chart.update()
191- })
192-
193- watchAll([chartData, context.range], (data, range) => {
194- const { lower, upper } = range
195- const slice = data
196- .filter(d => d.t > lower && d.t <= upper)
197- .map(d => d.y)
198- .sort((a, b) => a < b)
199-
200- var h = slice[0]
201- if (!h || h < 10) h = 10
202- else h = h + (5 - h % 5)
203- // set the height of the graph to a minimum or 10,
204- // or some multiple of 5 above the max height
205-
206- chart.options.scales.yAxes[0].ticks.max = h
207-
208- chart.update()
209- })
210-
211- context.range(range => {
212- const { lower, upper } = range
213-
214- chart.options.scales.xAxes[0].time.min = new Date(lower - DAY / 2)
215- chart.options.scales.xAxes[0].time.max = new Date(upper - DAY / 2)
216- // the squeezing in by DAY/2 is to stop data outside range from half showing
217-
218- chart.update()
219- })
220-
221138 return page
222139 }
140+}
223141
224- function viewBlog (blog) {
225- return () => api.history.sync.push(blog)
142+function getTitle ({ blog, mdRenderer }) {
143+ if (blog.value.content.title) return blog.value.content.title
144+ else if (blog.value.content.text) {
145+ var md = mdRenderer(marksum.title(blog.value.content.text))
146+ if (md && md.innerText) return md.innerText
226147 }
227-}
228148
229-function getTitle (blog) {
230- if (blog.value.content.title) return blog.value.content.title
231- else if (blog.value.content.text) return marksum.title(blog.value.content.text)
232- else return blog.key
149+ return blog.key
233150 }
234151
235-function fetchBlogs ({ server, store }) {
152+function fetchBlogData ({ server, store }) {
236153 pull(
237154 server.blogStats.readBlogs({ reverse: false }),
238155 pull.drain(blog => {
239156 store.blogs.push(blog)
@@ -241,34 +158,100 @@
241158 fetchComments({ server, store, blog })
242159 fetchLikes({ server, store, blog })
243160 })
244161 )
245-}
246162
247-function fetchComments ({ server, store, blog }) {
248- if (!store.comments.has(blog.key)) store.comments.put(blog.key, MutantArray())
163+ function fetchComments ({ server, store, blog }) {
164+ if (!store.comments.has(blog.key)) store.comments.put(blog.key, MutantArray())
249165
250- pull(
251- server.blogStats.readComments(blog),
252- pull.drain(msg => {
253- store.comments.get(blog.key).push(msg)
254- // TODO remove my comments from count?
255- })
256- )
166+ pull(
167+ server.blogStats.readComments(blog),
168+ pull.drain(msg => {
169+ store.comments.get(blog.key).push(msg)
170+ // TODO remove my comments from count?
171+ })
172+ )
173+ }
174+
175+ function fetchLikes ({ server, store, blog }) {
176+ if (!store.likes.has(blog.key)) store.likes.put(blog.key, MutantArray())
177+
178+ pull(
179+ server.blogStats.readLikes(blog),
180+ pull.drain(msg => {
181+ store.likes.get(blog.key).push(msg)
182+ // TODO this needs reducing... like + unlike are muddled in here
183+ // find any thing by same author
184+ // if exists - over-write or delete
185+ })
186+ )
187+ }
257188 }
258189
259-function fetchLikes ({ server, store, blog }) {
260- if (!store.likes.has(blog.key)) store.likes.put(blog.key, MutantArray())
190+function initialiseChart ({ canvas, context, foci }) {
191+ var chart = new Chart(canvas.getContext('2d'), chartConfig({ context }))
261192
262- pull(
263- server.blogStats.readLikes(blog),
264- pull.drain(msg => {
265- store.likes.get(blog.key).push(msg)
266- // TODO this needs reducing... like + unlike are muddled in here
267- // find any thing by same author
268- // if exists - over-write or delete
269- })
270- )
193+ const chartData = computed([context.focus, foci], (focus, foci) => {
194+ zeroGraphOnFocusChange(focus)
195+ const msgs = foci[focus]
196+ const grouped = groupBy(msgs, m => toDay(m.value.timestamp))
197+
198+ return Object.keys(grouped)
199+ .map(day => {
200+ return {
201+ t: day * DAY,
202+ y: grouped[day].length
203+ }
204+ })
205+ })
206+
207+ chartData(data => {
208+ chart.data.datasets[0].data = data
209+
210+ chart.update()
211+ })
212+
213+ watchAll([chartData, context.range], (data, range) => {
214+ const { lower, upper } = range
215+ const slice = data
216+ .filter(d => d.t > lower && d.t <= upper)
217+ .map(d => d.y)
218+ .sort((a, b) => a < b)
219+
220+ var h = slice[0]
221+ if (!h || h < 10) h = 10
222+ else h = h + (5 - h % 5)
223+ // set the height of the graph to a minimum or 10,
224+ // or some multiple of 5 above the max height
225+
226+ chart.options.scales.yAxes[0].ticks.max = h
227+
228+ chart.update()
229+ })
230+
231+ context.range(range => {
232+ const { lower, upper } = range
233+
234+ chart.options.scales.xAxes[0].time.min = new Date(lower - DAY / 2)
235+ chart.options.scales.xAxes[0].time.max = new Date(upper - DAY / 2)
236+ // the squeezing in by DAY/2 is to stop data outside range from half showing
237+
238+ chart.update()
239+ })
240+
241+ // ///// HELPERS /////
242+
243+ // HACK - if the focus has changed, then zero the data
244+ // this prevents the graph from showing some confusing animations when transforming between foci
245+ var lastFocus = context.focus()
246+ function zeroGraphOnFocusChange (focus) {
247+ if (focus !== lastFocus) {
248+ chart.data.datasets[0].data = []
249+ chart.update()
250+ lastFocus = focus
251+ }
252+ }
253+ function toDay (ts) { return Math.floor(ts / DAY) }
271254 }
272255
273256 // TODO rm chartData and other overly smart things which didn't work from here
274257 function chartConfig ({ context }) {
app/page/statsShow.mcssView
@@ -17,8 +17,9 @@
1717 section.totals {
1818 display: flex
1919
2020 div {
21+ flex-basis: 33%
2122 flex-grow: 1
2223
2324 cursor: pointer
2425 padding: 0 0 .5rem .5rem
@@ -75,24 +76,29 @@
7576 }
7677 }
7778
7879 table.blogs {
80+ width: 100%
7981 margin: 1rem 0
8082
8183 thead {
8284 tr {
8385 margin-bottom: 1rem
8486 color: hsl(0, 0%, 25%)
85- td {
86-
87+ th.details {
88+ width: 70%
89+ padding: 0 2rem 0 0
8790 }
91+ th.comments, th.likes, th.shares {
92+ width: 10%
93+ }
8894 }
8995 }
9096 tbody {
9197 tr.blog {
9298 margin-bottom: 1rem
9399 td.details {
94- width: 100%
100+ width: 70%
95101 padding: .8rem 2rem .8rem 0
96102 border-bottom: 1px solid rgba(0, 0, 0, .05)
97103
98104 div.title {
@@ -110,10 +116,11 @@
110116 text-decoration: underline
111117 }
112118 }
113119 }
114- td.comments, td.likes {
115- padding: 0 2.5rem
120+ td.comments, td.likes, td.shares {
121+ width: 10%
122+ /* padding: 0 2.5rem */
116123 font-size: 1.3rem
117124 font-weight: 600
118125 text-align: center
119126 }
ssb-server-blog-stats.jsView
@@ -37,11 +37,10 @@
3737 read: view.read,
3838 readBlogs,
3939 getBlogs,
4040 readComments,
41- readLikes,
42- // getLikes
43- // getComments
41+ readLikes
42+ // readShares
4443 }
4544
4645 function map (msg, seq) {
4746 var root
@@ -51,9 +50,8 @@
5150 if (isBlog(msg) && isMyMsg(msg)) return [['B', msg.key, getTimestamp(msg)]]
5251 else return []
5352
5453 case 'vote':
55- // process.stdout.write('L')
5654 root = getLikeRoot(msg)
5755 // TODO figure out how to only store likes I care about
5856 if (root) return [['L', root, getTimestamp(msg)]]
5957 else return []
@@ -62,9 +60,8 @@
6260 // - all likes, on all things D:
6361 // - likes AND unlikes
6462
6563 case 'post':
66- // process.stdout.write('POST ')
6764 root = getCommentRoot(msg)
6865 // TODO figure out how to only store comments I care about
6966 if (!root && isMyMsg(msg) && isPlog(msg)) return [['B', msg.key, getTimestamp(msg)]]
7067 else if (root) return [['C', root, getTimestamp(msg)]]
@@ -77,12 +74,11 @@
7774 return []
7875 }
7976 }
8077
81- // a Plog is a Blog shaped Post
78+ // a Plog is a Blog shaped Post!
8279 function isPlog (msg) {
83- // return false // Disable plogs
84- if (get(msg, 'value.content.text', '').length >= 3000) console.log(get(msg, 'value.content.text', '').length)
80+ // if (get(msg, 'value.content.text', '').length >= 3000) console.log(get(msg, 'value.content.text', '').length)
8581 return get(msg, 'value.content.text', '').length >= 3000
8682 }
8783
8884 function readBlogs (options = {}) {

Built with git-ssb-web