Files: 7d91615511c10dc2290684814abc7dd43da98917 / about / html / edit.js
6178 bytesRaw
1 | const nest = require('depnest') |
2 | const dataurl = require('dataurl-') |
3 | const hyperfile = require('hyperfile') |
4 | const hypercrop = require('hypercrop') |
5 | const hyperlightbox = require('hyperlightbox') |
6 | const Mutual = require('ssb-mutual') |
7 | |
8 | const { |
9 | h, Value, Dict: MutantObject, Struct, |
10 | map, computed, when, dictToCollection |
11 | } = require('mutant') |
12 | const pull = require('pull-stream') |
13 | |
14 | exports.gives = nest('about.html.edit') |
15 | |
16 | exports.needs = nest({ |
17 | 'about.obs': { |
18 | name: 'first', |
19 | imageUrl: 'first', |
20 | description: 'first' |
21 | }, |
22 | 'about.obs.groupedValues': 'first', |
23 | 'blob.sync.url': 'first', |
24 | 'keys.sync.id': 'first', |
25 | 'message.html.confirm': 'first', |
26 | 'message.html.markdown': 'first', |
27 | sbot: { |
28 | 'async.addBlob': 'first', |
29 | 'obs.connection': 'first', |
30 | 'pull.links': 'first' |
31 | } |
32 | }) |
33 | |
34 | exports.create = function (api) { |
35 | return nest({ |
36 | 'about.html.edit': edit |
37 | }) |
38 | |
39 | // TODO refactor this to use obs better |
40 | function edit (id) { |
41 | // TODO - get this to wait till the connection is present ! |
42 | var mutual = Mutual.init(api.sbot.obs.connection()) |
43 | |
44 | var avatar = Struct({ |
45 | current: api.about.obs.imageUrl(id), |
46 | new: MutantObject() |
47 | }) |
48 | |
49 | const links = api.sbot.pull.links |
50 | |
51 | var name = Struct({ |
52 | current: api.about.obs.name(id), |
53 | new: Value() |
54 | }) |
55 | |
56 | const images = computed(api.about.obs.groupedValues(id, 'image'), Object.keys) |
57 | |
58 | var namesRecord = MutantObject() |
59 | // TODO constrain query to one name per peer? |
60 | pull( |
61 | links({dest: id, rel: 'about', values: true}), |
62 | pull.map(e => e.value.content.name), |
63 | pull.filter(Boolean), |
64 | pull.drain(name => { |
65 | var n = namesRecord.get(name) || 0 |
66 | namesRecord.put(name, n + 1) |
67 | }) |
68 | ) |
69 | var names = dictToCollection(namesRecord) |
70 | |
71 | var lightbox = hyperlightbox() |
72 | |
73 | var isPossibleUpdate = computed([name.new, avatar.new], (name, avatar) => { |
74 | return name || avatar.link |
75 | }) |
76 | |
77 | var avatarSrc = computed([avatar], avatar => { |
78 | if (avatar.new.link) return api.blob.sync.url(avatar.new.link) |
79 | return avatar.current |
80 | }) |
81 | |
82 | var displayedName = computed([name], name => { |
83 | if (name.new) return name.new |
84 | else return name.current |
85 | }) |
86 | |
87 | var balances_div = h('div.balances') |
88 | |
89 | mutual.getAccountBalances(id, (error, balances) => { |
90 | if (balances == null) return '' |
91 | |
92 | var balance_els = [] |
93 | Object.keys(balances).forEach(function (key) { |
94 | balances_div.appendChild( |
95 | h('div', `💰 ${balances[key]} ${key}`) |
96 | ) |
97 | }) |
98 | }) |
99 | |
100 | return h('AboutEditor', [ |
101 | lightbox, |
102 | h('section.avatar', [ |
103 | h('section', [ |
104 | h('img', { src: avatarSrc }) |
105 | ]), |
106 | h('footer', displayedName) |
107 | ]), |
108 | h('section.description', computed(api.about.obs.description(id), (descr) => { |
109 | if (descr == null) return '' // TODO: should be in patchcore, I think... |
110 | return api.message.html.markdown(descr) |
111 | })), |
112 | h('section.credit', balances_div), |
113 | h('section.aliases', [ |
114 | h('header', 'Aliases'), |
115 | h('section.avatars', [ |
116 | h('header', 'Avatars'), |
117 | map(images, image => h('img', { |
118 | 'src': api.blob.sync.url(image), |
119 | 'ev-click': () => avatar.new.set({ link: image }) |
120 | })), |
121 | h('div.file-upload', [ |
122 | hyperfile.asDataURL(dataUrlCallback) |
123 | ]) |
124 | ]), |
125 | h('section.names', [ |
126 | h('header', 'Names'), |
127 | h('section', [ |
128 | map(names, n => h('div', { 'ev-click': () => name.new.set(n.key()) }, [ |
129 | h('div.name', n.key), |
130 | h('div.count', n.value) |
131 | ])), |
132 | h('input', { |
133 | placeholder: ' + another name', |
134 | 'ev-keyup': e => name.new.set(e.target.value) |
135 | }) |
136 | ]) |
137 | ]), |
138 | when(isPossibleUpdate, h('section.action', [ |
139 | h('button.cancel', { 'ev-click': clearNewSelections }, 'cancel'), |
140 | h('button.confirm', { 'ev-click': handleUpdateClick }, 'confirm changes') |
141 | ])) |
142 | ]) |
143 | ]) |
144 | |
145 | function dataUrlCallback (data) { |
146 | const cropCallback = (err, cropData) => { |
147 | if (err) throw err |
148 | if (!cropData) return lightbox.close() |
149 | |
150 | var _data = dataurl.parse(cropData) |
151 | api.sbot.async.addBlob(pull.once(_data.data), (err, hash) => { |
152 | if (err) throw err // TODO check if this is safely caught by error catcher |
153 | |
154 | avatar.new.set({ |
155 | link: hash, |
156 | size: _data.data.length, |
157 | type: _data.mimetype, |
158 | width: 512, |
159 | height: 512 |
160 | }) |
161 | }) |
162 | lightbox.close() |
163 | } |
164 | |
165 | const cropEl = Crop(data, cropCallback) |
166 | lightbox.show(cropEl) |
167 | } |
168 | |
169 | function Crop (data, cb) { |
170 | var img = h('img', {src: data}) |
171 | |
172 | var crop = h('div') |
173 | |
174 | waitForImg() |
175 | |
176 | return h('div.cropper', [ |
177 | crop, |
178 | h('div.background') |
179 | ]) |
180 | |
181 | function waitForImg () { |
182 | // WEIRDNESS - if you invoke hypecrop before img is ready, |
183 | // the canvas instantiates and draws nothing |
184 | |
185 | if (!img.height && !img.width) { |
186 | return window.setTimeout(waitForImg, 100) |
187 | } |
188 | |
189 | var canvas = hypercrop(img) |
190 | crop = ( |
191 | h('PatchProfileCrop', [ |
192 | h('header', 'click and drag to crop your image'), |
193 | canvas, |
194 | h('section.actions', [ |
195 | h('Button', { 'ev-click': () => cb() }, 'Cancel'), |
196 | h('Button -primary', { 'ev-click': () => cb(null, canvas.selection.toDataURL()) }, 'Okay') |
197 | ]) |
198 | ]) |
199 | ) |
200 | } |
201 | } |
202 | |
203 | function clearNewSelections () { |
204 | name.new.set(null) |
205 | avatar.new.set({}) |
206 | } |
207 | |
208 | function handleUpdateClick () { |
209 | const newName = name.new() |
210 | const newAvatar = avatar.new() |
211 | |
212 | const msg = { |
213 | type: 'about', |
214 | about: id |
215 | } |
216 | |
217 | if (newName) msg.name = newName |
218 | if (newAvatar.link) msg.image = newAvatar |
219 | |
220 | api.message.html.confirm(msg, (err, data) => { |
221 | if (err) return console.error(err) |
222 | |
223 | clearNewSelections() |
224 | |
225 | // TODO - update aliases displayed |
226 | }) |
227 | } |
228 | } |
229 | } |
230 |
Built with git-ssb-web