git ssb

16+

Dominic / patchbay



Tree: a60e7b14149be697b85339ef00dfadf4059ec79a

Files: a60e7b14149be697b85339ef00dfadf4059ec79a / about / html / edit.js

6181 bytesRaw
1const nest = require('depnest')
2const dataurl = require('dataurl-')
3const hyperfile = require('hyperfile')
4const hypercrop = require('hypercrop')
5const hyperlightbox = require('hyperlightbox')
6const Mutual = require('ssb-mutual')
7
8const {
9 h, Value, Dict: MutantObject, Struct,
10 map, computed, when, dictToCollection
11} = require('mutant')
12const pull = require('pull-stream')
13
14exports.gives = nest('about.html.edit')
15
16exports.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
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 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
88 var balances_div = h('div.balances')
89
90 mutual.getAccountBalances(id, (error, balances) => {
91 if (balances == null) return ''
92
93 var balance_els = [];
94 Object.keys(balances).forEach(function(key) {
95 balances_div.appendChild(
96 h('div', `💰 ${balances[key]} ${key}`)
97 )
98 });
99 })
100
101 return h('AboutEditor', [
102 lightbox,
103 h('section.avatar', [
104 h('section', [
105 h('img', { src: avatarSrc })
106 ]),
107 h('footer', displayedName)
108 ]),
109 h('section.description', computed(api.about.obs.description(id), (descr) => {
110 if (descr == null) return '' // TODO: should be in patchcore, I think...
111 return api.message.html.markdown(descr)
112 })),
113 h('section.credit', balances_div),
114 h('section.aliases', [
115 h('header', 'Aliases'),
116 h('section.avatars', [
117 h('header', 'Avatars'),
118 map(images, image => h('img', {
119 'src': api.blob.sync.url(image),
120 'ev-click': () => avatar.new.set({ link: image })
121 })),
122 h('div.file-upload', [
123 hyperfile.asDataURL(dataUrlCallback)
124 ])
125 ]),
126 h('section.names', [
127 h('header', 'Names'),
128 h('section', [
129 map(names, n => h('div', { 'ev-click': () => name.new.set(n.key()) }, [
130 h('div.name', n.key),
131 h('div.count', n.value)
132 ])),
133 h('input', {
134 placeholder: ' + another name',
135 'ev-keyup': e => name.new.set(e.target.value)
136 })
137 ])
138 ]),
139 when(isPossibleUpdate, h('section.action', [
140 h('button.cancel', { 'ev-click': clearNewSelections }, 'cancel'),
141 h('button.confirm', { 'ev-click': handleUpdateClick }, 'confirm changes')
142 ]))
143 ])
144 ])
145
146 function dataUrlCallback (data) {
147 const cropCallback = (err, cropData) => {
148 if (err) throw err
149 if (!cropData) return lightbox.close()
150
151 var _data = dataurl.parse(cropData)
152 api.sbot.async.addBlob(pull.once(_data.data), (err, hash) => {
153 if (err) throw err // TODO check if this is safely caught by error catcher
154
155 avatar.new.set({
156 link: hash,
157 size: _data.data.length,
158 type: _data.mimetype,
159 width: 512,
160 height: 512
161 })
162 })
163 lightbox.close()
164 }
165
166 const cropEl = Crop(data, cropCallback)
167 lightbox.show(cropEl)
168 }
169
170 function Crop (data, cb) {
171 var img = h('img', {src: data})
172
173 var crop = h('div')
174
175 waitForImg()
176
177 return h('div.cropper', [
178 crop,
179 h('div.background')
180 ])
181
182 function waitForImg () {
183 // WEIRDNESS - if you invoke hypecrop before img is ready,
184 // the canvas instantiates and draws nothing
185
186 if (!img.height && !img.width) {
187 return window.setTimeout(waitForImg, 100)
188 }
189
190 var canvas = hypercrop(img)
191 crop = (
192 h('PatchProfileCrop', [
193 h('header', 'click and drag to crop your image'),
194 canvas,
195 h('section.actions', [
196 h('Button', { 'ev-click': () => cb() }, 'Cancel'),
197 h('Button -primary', { 'ev-click': () => cb(null, canvas.selection.toDataURL()) }, 'Okay')
198 ])
199 ])
200 )
201 }
202 }
203
204 function clearNewSelections () {
205 name.new.set(null)
206 avatar.new.set({})
207 }
208
209 function handleUpdateClick () {
210 const newName = name.new()
211 const newAvatar = avatar.new()
212
213 const msg = {
214 type: 'about',
215 about: id
216 }
217
218 if (newName) msg.name = newName
219 if (newAvatar.link) msg.image = newAvatar
220
221 api.message.html.confirm(msg, (err, data) => {
222 if (err) return console.error(err)
223
224 clearNewSelections()
225
226 // TODO - update aliases displayed
227 })
228 }
229 }
230}
231
232

Built with git-ssb-web