Files: edcc95e36db325ce7977d1fe7522bf3633615898 / views / show.js
6391 bytesRaw
1 | const { h, Struct, computed, resolve } = require('mutant') |
2 | const pull = require('pull-stream') |
3 | const printTime = require('../lib/print-time') |
4 | |
5 | module.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 | h('div.closes-at', [ |
22 | 'Closes at ', |
23 | computed(state.now.closesAt, t => t ? t.toString() : '') |
24 | ]), |
25 | ScryShowResults(), |
26 | h('div.actions', [ |
27 | PublishBtn() |
28 | ]) |
29 | ]) |
30 | |
31 | function PublishBtn () { |
32 | return computed([state.now, state.next], (current, next) => { |
33 | if (!next.isEditing) return |
34 | if (next.isPublishing) return h('button', h('i.fa.fa-spin.fa-pulse')) |
35 | |
36 | const newPosition = current.position.join() !== next.position.join() |
37 | return h('button', |
38 | { |
39 | className: newPosition ? '-primary' : '', |
40 | disabled: !newPosition, |
41 | 'ev-click': () => { |
42 | state.next.isPublishing.set(true) |
43 | const choices = next.position.reduce((acc, el, i) => { |
44 | if (el) acc.push(i) |
45 | return acc |
46 | }, []) |
47 | |
48 | scuttle.position.async.publishMeetingTime({ poll, choices }, (err, data) => { |
49 | if (err) throw err |
50 | console.log(data) |
51 | }) |
52 | } |
53 | }, 'Publish' |
54 | ) |
55 | }) |
56 | } |
57 | |
58 | function ScryShowResults () { |
59 | return computed(state.now, ({ title, closesAt, times, rows }) => { |
60 | const style = { |
61 | display: 'grid', |
62 | 'grid-template-columns': `minmax(10rem, auto) repeat(${times.length}, 4rem)` |
63 | } |
64 | |
65 | return [ |
66 | h('ScryShowResults', { style }, [ |
67 | times.map(ScryShowTime), |
68 | ScryShowSummary(rows), |
69 | rows.map(ScryShowRow) |
70 | ]) |
71 | ] |
72 | }) |
73 | } |
74 | |
75 | function ScryShowRow ({ author, position }) { |
76 | if (author !== myFeedId) { |
77 | return [ |
78 | h('div.about', [ |
79 | avatar(author), |
80 | name(author) |
81 | ]), |
82 | position.map(pos => pos |
83 | ? h('div.position.-yes', tick()) |
84 | : h('div.position.-no') |
85 | ) |
86 | ] |
87 | } |
88 | |
89 | const toggleEditing = () => { |
90 | const isEditing = !resolve(state.next.isEditing) |
91 | state.next.isEditing.set(isEditing) |
92 | } |
93 | |
94 | return [ |
95 | h('div.about', [ |
96 | avatar(author), |
97 | name(author), |
98 | h('i.fa.fa-pencil', { 'ev-click': toggleEditing }) |
99 | ]), |
100 | computed([state.next, state.now.position], ({ isEditing, position }, currentPosition) => { |
101 | if (isEditing) { |
102 | return position.map((pos, i) => { |
103 | return h('div.position.-edit', |
104 | { |
105 | 'ev-click': () => { |
106 | const nextPosition = resolve(state.next.position) |
107 | nextPosition[i] = !pos |
108 | state.next.position.set(nextPosition) |
109 | } |
110 | }, |
111 | pos ? checkedBox() : uncheckedBox() |
112 | ) |
113 | }) |
114 | } |
115 | |
116 | return currentPosition.map(pos => pos |
117 | ? h('div.position.-yes', tick()) |
118 | : h('div.position.-no') |
119 | ) |
120 | }) |
121 | ] |
122 | } |
123 | |
124 | function ScryShowSummary (rows) { |
125 | if (!rows.length) return |
126 | |
127 | const participants = rows.filter(r => r.position[0] !== null).length |
128 | |
129 | const counts = rows[0].position.map((_, i) => { |
130 | return rows.reduce((acc, row) => { |
131 | if (row.position[i] === true) acc += 1 |
132 | return acc |
133 | }, 0) |
134 | }) |
135 | return [ |
136 | h('div.participants', participants === 1 |
137 | ? `${participants} participant` |
138 | : `${participants} participants` |
139 | ), |
140 | counts.map(n => h('div.count', `${n}${tick()}`)) |
141 | ] |
142 | } |
143 | |
144 | function fetchState () { |
145 | scuttle.poll.async.get(poll.key, (err, doc) => { |
146 | if (err) return console.error(err) |
147 | |
148 | const { title, closesAt, positions } = doc |
149 | const times = doc.results.map(result => result.choice) |
150 | const results = times.map(t => doc.results.find(result => result.choice === t)) |
151 | // this ensures results Array is in same order as a times Array |
152 | |
153 | const rows = positions |
154 | .reduce((acc, pos) => { |
155 | if (!acc.includes(pos.value.author)) acc.push(pos.value.author) |
156 | return acc |
157 | }, []) |
158 | .map(author => { |
159 | const position = times.map((time, i) => { |
160 | return results[i].voters.hasOwnProperty(author) |
161 | }) |
162 | return { author, position } |
163 | }) |
164 | |
165 | const myRow = rows.find(r => r.author === myFeedId) |
166 | const myPosition = myRow ? myRow.position : Array(times.length).fill(null) |
167 | |
168 | var isEditing = false |
169 | if (!myRow) { |
170 | rows.push({ author: myFeedId, position: myPosition }) |
171 | isEditing = true |
172 | } |
173 | |
174 | state.now.set({ |
175 | title, |
176 | closesAt, |
177 | times, |
178 | rows, |
179 | position: myPosition |
180 | }) |
181 | state.next.set({ |
182 | position: Array.from(myPosition), |
183 | isEditing, |
184 | isPublishing: false |
185 | }) |
186 | }) |
187 | } |
188 | |
189 | function watchForUpdates (cb) { |
190 | // TODO check if isEditing before calling cb |
191 | // start a loop to trigger cb after finished editing |
192 | pull( |
193 | scuttle.poll.pull.updates(poll.key), |
194 | pull.filter(m => !m.sync), |
195 | pull.drain(m => { |
196 | cb() |
197 | }) |
198 | ) |
199 | } |
200 | |
201 | function tick () { return '✔' } |
202 | function checkedBox () { return testing ? '☑' : h('i.fa.fa-check-square-o') } |
203 | function uncheckedBox () { return testing ? '☐' : h('i.fa.fa-square-o') } |
204 | } |
205 | |
206 | function initialState () { |
207 | return { |
208 | now: Struct({ |
209 | title: '', |
210 | times: [], |
211 | closesAt: undefined, |
212 | rows: [], |
213 | position: [] |
214 | }), |
215 | next: Struct({ |
216 | position: [], |
217 | isEditing: false, |
218 | isPublishing: false |
219 | }) |
220 | } |
221 | } |
222 | |
223 | // component: show-time |
224 | |
225 | function ScryShowTime (time, i) { |
226 | const style = { 'grid-column': i + 2 } |
227 | |
228 | return h('ScryShowTime', { style }, [ |
229 | h('div.month', month(time)), |
230 | h('div.date', time.getDate()), |
231 | h('div.day', day(time)), |
232 | h('div.time', printTime(time)) |
233 | ]) |
234 | } |
235 | |
236 | function month (date) { |
237 | const months = ['Jan', 'Feb', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
238 | |
239 | return months[date.getMonth()] |
240 | } |
241 | |
242 | function day (date) { |
243 | const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thu', 'Fri', 'Sat'] |
244 | |
245 | return days[date.getDay()] |
246 | } |
247 |
Built with git-ssb-web