git ssb

1+

Daan Patchwork / patchwork



Tree: 4d9f238ff73f7136cd292da88f06c17fe1a8c445

Files: 4d9f238ff73f7136cd292da88f06c17fe1a8c445 / lib / depject / gathering / sheet / edit.js

8033 bytesRaw
1const nest = require('depnest')
2const extend = require('xtend')
3const Pickr = require('flatpickr')
4const spacetime = require('spacetime')
5const displaySheet = require('../../../sheet/display')
6
7const { Value, h, computed, when } = require('mutant')
8
9exports.gives = nest('gathering.sheet.edit')
10
11exports.needs = nest({
12 'message.sheet.preview': 'first',
13 'keys.sync.id': 'first',
14 'sbot.async.publish': 'first',
15 'sbot.async.get': 'first',
16 'about.async.latestValues': 'first',
17 'blob.html.input': 'first',
18 'blob.sync.url': 'first',
19 'intl.sync.i18n': 'first',
20 'suggest.hook': 'first'
21})
22
23exports.create = function (api) {
24 const i18n = api.intl.sync.i18n
25 return nest('gathering.sheet.edit', function (id) {
26 getCurrentValues(id, (err, msg) => {
27 if (err) {
28 return showDialog({
29 type: 'info',
30 title: i18n('Update Gathering'),
31 buttons: [i18n('OK')],
32 message: i18n('Cannot load gathering'),
33 detail: err.stack
34 })
35 }
36
37 // use private gathering recps for default profile suggestions
38 const participants = msg.value && msg.value.content && msg.value.content.recps
39
40 displaySheet(close => {
41 const publishing = Value(false)
42
43 const chosen = {
44 title: Value(msg.gathering.title),
45 startDateTime: Value(msg.gathering.startDateTime),
46 image: Value(msg.gathering.image),
47 description: Value(msg.gathering.description)
48 }
49
50 const imageUrl = computed(chosen.image, (id) => id && api.blob.sync.url(id))
51
52 return {
53 content: h('div', {
54 style: {
55 padding: '20px',
56 'text-align': 'center'
57 }
58 }, [
59 h('h2', {
60 style: {
61 'font-weight': 'normal'
62 }
63 }, [id ? i18n('Edit Gathering') : i18n('Create Gathering')]),
64 h('GatheringEditor', [
65 h('input.title', {
66 placeholder: i18n('Choose a title'),
67 hooks: [ValueHook(chosen.title), FocusHook()]
68 }),
69 h('input.date', {
70 placeholder: i18n('Choose date and time'),
71 hooks: [
72 PickrHook(chosen.startDateTime)
73 ]
74 }),
75 h('ImageInput .banner', {
76 style: { 'background-image': computed(imageUrl, x => `url(${x})`) }
77 }, [
78 h('span', ['🖼 ', i18n('Choose Banner Image...')]),
79 api.blob.html.input((err, file) => {
80 if (err) return
81 chosen.image.set(file)
82 }, {
83 accept: 'image/*'
84 })
85 ]),
86 h('textarea.description', {
87 placeholder: i18n('Describe the gathering (if you want)'),
88 hooks: [ValueHook(chosen.description), api.suggest.hook({ participants })]
89 })
90 ])
91 ]),
92 footer: [
93 h('button -save', {
94 'ev-click': save,
95 disabled: publishing
96 }, when(publishing, i18n('Publishing...'), i18n('Preview & Publish'))),
97 h('button -cancel', {
98 'ev-click': close
99 }, i18n('Cancel'))
100 ]
101 }
102
103 function ensureExists (cb) {
104 if (!id) {
105 api.sbot.async.publish({
106 type: 'gathering'
107 }, (err, msg) => {
108 if (err) return cb(err)
109 cb(null, msg.key)
110 })
111 } else {
112 cb(null, id)
113 }
114 }
115
116 function save () {
117 // no confirm
118 const update = {}
119
120 if (!compareImage(chosen.image(), msg.gathering.image)) update.image = chosen.image()
121 if (!compareTime(chosen.startDateTime(), msg.gathering.startDateTime)) update.startDateTime = chosen.startDateTime()
122 if (chosen.title() !== msg.gathering.title) update.title = chosen.title() || i18n('Untitled Gathering')
123 if (chosen.description() !== msg.gathering.description) update.description = chosen.description()
124
125 if (Object.keys(update).length) {
126 // gatherings consist of multiple messages (maybe none of them exist yet), so we need to
127 // construct the preview dialog manually, and override the about values
128 api.message.sheet.preview({
129 key: id,
130 previewAbout: update,
131 publiclyEditable: true,
132 value: {
133 author: api.keys.sync.id(),
134 private: false, // patchwork can only make public gatherings
135 content: {
136 type: 'gathering',
137 recps: participants
138 }
139 }
140 }, (err, confirmed) => {
141 if (err) throw err
142 if (confirmed) {
143 publish(update)
144 }
145 })
146 } else {
147 showDialog({
148 type: 'info',
149 title: i18n('Update Gathering'),
150 buttons: [i18n('OK')],
151 message: i18n('Nothing to publish'),
152 detail: i18n('You have not made any changes.')
153 })
154 close()
155 }
156 }
157
158 function publish (update) {
159 publishing.set(true)
160 ensureExists((err, id) => {
161 if (err) throw err
162 const content = extend({
163 type: 'about',
164 about: id
165 }, update)
166
167 // keep private gatherings private!
168 if (msg.value && msg.value.content && msg.value.content.recps) {
169 content.recps = msg.value.content.recps
170 }
171
172 api.sbot.async.publish(content, (err) => {
173 if (err) {
174 publishing.set(false)
175 showDialog({
176 type: 'error',
177 title: i18n('Error'),
178 buttons: ['OK'],
179 message: i18n('An error occurred while attempting to publish gathering.'),
180 detail: err.message
181 })
182 } else {
183 close()
184 }
185 })
186 })
187 }
188 })
189 })
190 })
191
192 function getCurrentValues (id, cb) {
193 if (id) {
194 api.sbot.async.get({ id, private: true }, (err, value) => {
195 if (err) return cb(err)
196 if (value.content.type === 'gathering') {
197 api.about.async.latestValues(id, ['title', 'startDateTime', 'image', 'description'], (err, gathering) => {
198 if (err) return cb(err)
199 cb(null, { key: id, value, gathering })
200 })
201 } else {
202 cb(new Error('Message must be of type "gathering"'))
203 }
204 })
205 } else {
206 cb(null, { gathering: {} })
207 }
208 }
209}
210
211function compareTime (a, b) {
212 if (!a && !b) {
213 return true
214 } else if (!a || !b) {
215 return false
216 } else {
217 return a.epoch === b.epoch
218 }
219}
220
221function compareImage (a, b) {
222 a = isObject(a) ? a.link : a
223 b = isObject(b) ? b.link : b
224 return a === b
225}
226
227function isObject (value) {
228 return value && typeof value === 'object'
229}
230
231function FocusHook () {
232 return function (element) {
233 setTimeout(() => {
234 element.focus()
235 element.select()
236 }, 5)
237 }
238}
239
240function ValueHook (obs) {
241 return function (element) {
242 element.value = obs()
243 element.oninput = function () {
244 obs.set(element.value.trim())
245 }
246 }
247}
248
249function showDialog (opts) {
250 const electron = require('electron')
251 electron.remote.dialog.showMessageBox(electron.remote.getCurrentWindow(), opts)
252}
253
254function PickrHook (obs) {
255 return function (element) {
256 const picker = Pickr(element, {
257 enableTime: true,
258 altInput: true,
259 dateFormat: 'U',
260 onChange: function () {
261 obs.set(spacetime(parseInt(element.value, 10) * 1000))
262 }
263 })
264
265 const value = obs()
266 if (value) {
267 picker.setDate(value.epoch)
268 }
269 return () => picker.destroy()
270 }
271}
272

Built with git-ssb-web