git ssb

0+

mixmix / patchbay-scry



Tree: 83748629af52473497dd0896a5c487f66982c8ff

Files: 83748629af52473497dd0896a5c487f66982c8ff / views / show.js

8113 bytesRaw
1const { h, Struct, computed, resolve } = require('mutant')
2const pull = require('pull-stream')
3const printTime = require('../lib/print-time')
4
5module.exports = function ScryShow (opts) {
6 const {
7 poll,
8 myFeedId,
9 scuttle,
10 name = k => k.slice(0, 9),
11 avatar = k => h('img'),
12 testing = false
13 } = opts
14
15 const state = Struct(initialState())
16 fetchState()
17 watchForUpdates(fetchState)
18
19 return h('ScryShow', [
20 h('h1', state.now.title),
21 ScryShowClosesAt(state.now),
22 ScryShowResults(),
23 h('div.actions', [
24 PublishBtn()
25 ])
26 ])
27
28 function PublishBtn () {
29 return computed([state.now, state.next], (current, next) => {
30 if (current.resolution) return
31 if (!next.isEditing) return
32 if (next.isPublishing) return h('button', h('i.fa.fa-spin.fa-pulse'))
33
34 const newPosition = current.position.join() !== next.position.join()
35 return h('button',
36 {
37 className: newPosition ? '-primary' : '',
38 disabled: !newPosition,
39 'ev-click': () => {
40 state.next.isPublishing.set(true)
41 const choices = next.position.reduce((acc, el, i) => {
42 if (el) acc.push(i)
43 return acc
44 }, [])
45
46 scuttle.position.async.publishMeetingTime({ poll, choices }, (err, data) => {
47 if (err) throw err
48 console.log(data)
49 })
50 }
51 }, 'Publish'
52 )
53 })
54 }
55
56 function ScryShowResults () {
57 return computed(state.now, ({ title, closesAt, times, rows, resolution }) => {
58 const style = {
59 display: 'grid',
60 'grid-template-columns': `minmax(10rem, auto) repeat(${times.length}, 4rem)`
61 }
62
63 const getChosenClass = i => {
64 if (!resolution) return ''
65 return resolution.choices.includes(i) ? '-chosen' : '-not-chosen'
66 }
67
68 return [
69 h('ScryShowResults', { style }, [
70 ScryShowTimes(times, getChosenClass),
71 ScryShowResolution(times, resolution),
72 ScryShowSummary(rows, getChosenClass),
73 ScryShowPositions(rows)
74 ])
75 ]
76 })
77 }
78
79 function ScryShowPositions (rows) {
80 return rows.map(({ author, position }) => {
81 if (author !== myFeedId) return OtherPosition(author, position)
82 else return MyPosition(position)
83 })
84
85 function OtherPosition (author, position) {
86 return [
87 h('div.about', [
88 avatar(author),
89 name(author)
90 ]),
91 position.map(pos => pos
92 ? h('div.position.-yes', tick())
93 : h('div.position.-no')
94 )
95 ]
96 }
97
98 function MyPosition (position) {
99 const toggleEditing = () => {
100 const isEditing = !resolve(state.next.isEditing)
101 state.next.isEditing.set(isEditing)
102 }
103
104 return [
105 h('div.about', [
106 avatar(myFeedId),
107 name(myFeedId),
108 h('i.fa.fa-pencil', { 'ev-click': toggleEditing })
109 ]),
110 computed([state.next, state.now.position], ({ isEditing, position }, currentPosition) => {
111 if (!isEditing) {
112 return currentPosition.map(pos => pos
113 ? h('div.position.-yes', tick())
114 : h('div.position.-no')
115 )
116 }
117
118 return position.map((pos, i) => {
119 return h('div.position.-edit',
120 {
121 'ev-click': () => {
122 const nextPosition = resolve(state.next.position)
123 nextPosition[i] = !pos
124 state.next.position.set(nextPosition)
125 }
126 },
127 pos ? checkedBox() : uncheckedBox()
128 )
129 })
130 })
131 ]
132 }
133 }
134
135 function ScryShowResolution (times, resolution) {
136 if (!resolution) return
137
138 return times.map((_, i) => {
139 const style = { 'grid-column': i + 2 } // grid-columns start at 1 D:
140 const isChoice = resolution.choices.includes(i)
141 const className = isChoice ? '-chosen' : ''
142
143 return h('div.resolution', { style, className },
144 isChoice ? star() : ''
145 )
146 })
147 }
148
149 function ScryShowSummary (rows, getChosenClass) {
150 if (!rows.length) return
151
152 const participants = rows.filter(r => r.position[0] !== null).length
153
154 const counts = rows[0].position.map((_, i) => {
155 return rows.reduce((acc, row) => {
156 if (row.position[i] === true) acc += 1
157 return acc
158 }, 0)
159 })
160 return [
161 h('div.participants', participants === 1
162 ? `${participants} participant`
163 : `${participants} participants`
164 ),
165 counts.map((n, i) => {
166 return h('div.count', { className: getChosenClass(i) },
167 `${n}${tick()}`
168 )
169 })
170 ]
171 }
172
173 function fetchState () {
174 scuttle.poll.async.get(poll.key, (err, doc) => {
175 if (err) return console.error(err)
176
177 const { title, closesAt, positions } = doc
178 const times = doc.results.map(result => result.choice)
179 const results = times.map(t => doc.results.find(result => result.choice === t))
180 // this ensures results Array is in same order as a times Array
181
182 const rows = positions
183 .reduce((acc, pos) => {
184 if (!acc.includes(pos.value.author)) acc.push(pos.value.author)
185 return acc
186 }, [])
187 .map(author => {
188 const position = times.map((time, i) => {
189 return results[i].voters.hasOwnProperty(author)
190 })
191 return { author, position }
192 })
193
194 const myRow = rows.find(r => r.author === myFeedId)
195 const myPosition = myRow ? myRow.position : Array(times.length).fill(null)
196
197 var isEditing = false
198 if (!myRow && !doc.resolution) {
199 rows.push({ author: myFeedId, position: myPosition })
200 isEditing = true
201 }
202
203 state.now.set({
204 title,
205 closesAt,
206 times,
207 rows,
208 resolution: doc.resolution,
209 position: myPosition
210 })
211 state.next.set({
212 position: Array.from(myPosition),
213 isEditing,
214 isPublishing: false
215 })
216 })
217 }
218
219 function watchForUpdates (cb) {
220 // TODO check if isEditing before calling cb
221 // start a loop to trigger cb after finished editing
222 pull(
223 scuttle.poll.pull.updates(poll.key),
224 pull.filter(m => !m.sync),
225 pull.drain(m => {
226 cb()
227 })
228 )
229 }
230
231 function tick () { return '✔' }
232 function checkedBox () { return testing ? '☑' : h('i.fa.fa-check-square-o') }
233 function uncheckedBox () { return testing ? '☐' : h('i.fa.fa-square-o') }
234 function star () { return testing ? '★' : h('i.fa.fa-star') }
235}
236
237function initialState () {
238 return {
239 now: Struct({
240 title: '',
241 times: [],
242 closesAt: undefined,
243 resolution: undefined,
244 rows: [],
245 position: []
246 }),
247 next: Struct({
248 position: [],
249 isEditing: false,
250 isPublishing: false
251 })
252 }
253}
254
255function ScryShowClosesAt ({ closesAt, resolution }) {
256 return h('div.closes-at', computed([closesAt, resolution], (t, resolution) => {
257 if (resolution) return
258 if (!t) return
259
260 const distance = t - new Date()
261 if (distance < 0) return 'This scry has closed, but a resolution has yet to be declared.'
262
263 const hours = Math.floor(distance / (60 * 60e3))
264 const days = Math.floor(hours / 24)
265 return `This scry closes in ${days} days, ${hours % 24} hours`
266 }))
267}
268
269// component: show-time
270
271function ScryShowTimes (times, getChosenClass) {
272 return times.map((time, i) => {
273 const style = { 'grid-column': i + 2 } // grid-columns start at 1 D:
274
275 return h('ScryShowTime', { style, className: getChosenClass(i) }, [
276 h('div.month', month(time)),
277 h('div.date', time.getDate()),
278 h('div.day', day(time)),
279 h('div.time', printTime(time))
280 ])
281 })
282}
283
284function month (date) {
285 const months = ['Jan', 'Feb', 'March', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']
286
287 return months[date.getMonth()]
288}
289
290function day (date) {
291 const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thu', 'Fri', 'Sat']
292
293 return days[date.getDay()]
294}
295

Built with git-ssb-web