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