git ssb

16+

Dominic / patchbay



Tree: 8c7cce708fd7f6b4cc01356d01c5a8bfa1fad5e2

Files: 8c7cce708fd7f6b4cc01356d01c5a8bfa1fad5e2 / about / html / edit.js

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

Built with git-ssb-web