git ssb

16+

Dominic / patchbay



Tree: 9132118089771e8d960b2590aece46b1b7f121a7

Files: 9132118089771e8d960b2590aece46b1b7f121a7 / about / html / edit.js

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

Built with git-ssb-web