Files: f9e0b4147671e0c2b3ac312227259f4fdcaf8172 / about / html / edit.js
5116 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 { |
7 | h, Value, Array: MutantArray, Dict: MutantObject, Struct, |
8 | map, computed, when, dictToCollection |
9 | } = require('mutant') |
10 | const pull = require('pull-stream') |
11 | |
12 | exports.gives = nest('about.html.edit') |
13 | |
14 | exports.needs = nest({ |
15 | 'about.obs': { |
16 | name: 'first', |
17 | imageUrl: 'first', |
18 | description: 'first' |
19 | }, |
20 | 'about.obs.groupedValues': 'first', |
21 | 'blob.sync.url': 'first', |
22 | 'keys.sync.id': 'first', |
23 | 'message.html.confirm': 'first', |
24 | 'message.html.markdown': 'first', |
25 | sbot: { |
26 | 'async.addBlob': 'first', |
27 | 'pull.links': 'first' |
28 | } |
29 | }) |
30 | |
31 | exports.create = function (api) { |
32 | return nest({ |
33 | 'about.html.edit': edit |
34 | }) |
35 | |
36 | // TODO refactor this to use obs better |
37 | function edit (id) { |
38 | var avatar = Struct({ |
39 | current: api.about.obs.imageUrl(id), |
40 | new: MutantObject() |
41 | }) |
42 | |
43 | const links = api.sbot.pull.links |
44 | |
45 | var name = Struct({ |
46 | current: api.about.obs.name(id), |
47 | new: Value() |
48 | }) |
49 | |
50 | const images = computed(api.about.obs.groupedValues(id, 'image'), Object.keys) |
51 | |
52 | var namesRecord = MutantObject() |
53 | // TODO constrain query to one name per peer? |
54 | pull( |
55 | links({dest: id, rel: 'about', values: true}), |
56 | pull.map(e => e.value.content.name), |
57 | pull.filter(Boolean), |
58 | pull.drain(name => { |
59 | var n = namesRecord.get(name) || 0 |
60 | namesRecord.put(name, n + 1) |
61 | }) |
62 | ) |
63 | var names = dictToCollection(namesRecord) |
64 | |
65 | var lb = hyperlightbox() |
66 | |
67 | var isPossibleUpdate = computed([name.new, avatar.new], (name, avatar) => { |
68 | return name || avatar.link |
69 | }) |
70 | |
71 | var avatarSrc = computed([avatar], avatar => { |
72 | if (avatar.new.link) return api.blob.sync.url(avatar.new.link) |
73 | return avatar.current |
74 | }) |
75 | |
76 | var displayedName = computed([name], name => { |
77 | if (name.new) return name.new |
78 | else return name.current |
79 | }) |
80 | |
81 | return h('AboutEditor', [ |
82 | h('section.lightbox', lb), |
83 | h('section.avatar', [ |
84 | h('section', [ |
85 | h('img', { src: avatarSrc }) |
86 | ]), |
87 | h('footer', displayedName) |
88 | ]), |
89 | h('section.description', computed(api.about.obs.description(id), (descr) => { |
90 | if (descr == null) return '' // TODO: should be in patchcore, I think... |
91 | return api.message.html.markdown(descr) |
92 | })), |
93 | h('section.aliases', [ |
94 | h('header', 'Aliases'), |
95 | h('section.avatars', [ |
96 | h('header', 'Avatars'), |
97 | map(images, image => h('img', { |
98 | 'src': api.blob.sync.url(image), |
99 | 'ev-click': () => avatar.new.set({ link: image }) |
100 | })), |
101 | h('div.file-upload', [ |
102 | hyperfile.asDataURL(dataUrlCallback) |
103 | ]) |
104 | ]), |
105 | h('section.names', [ |
106 | h('header', 'Names'), |
107 | h('section', [ |
108 | map(names, n => h('div', { 'ev-click': () => name.new.set(n.key()) }, [ |
109 | h('div.name', n.key), |
110 | h('div.count', n.value) |
111 | ])), |
112 | h('input', { |
113 | placeholder: ' + another name', |
114 | 'ev-keyup': e => name.new.set(e.target.value) |
115 | }) |
116 | ]) |
117 | ]), |
118 | when(isPossibleUpdate, h('section.action', [ |
119 | h('button.cancel', { 'ev-click': clearNewSelections }, 'cancel'), |
120 | h('button.confirm', { 'ev-click': handleUpdateClick }, 'confirm changes') |
121 | ])) |
122 | ]) |
123 | ]) |
124 | |
125 | function dataUrlCallback (data) { |
126 | var el = crop(data, (err, data) => { |
127 | if (err) throw err |
128 | |
129 | if (data) { |
130 | var _data = dataurl.parse(data) |
131 | |
132 | api.sbot.async.addBlob(pull.once(_data.data), (err, hash) => { |
133 | if (err) throw err // TODO check if this is safely caught by error catcher |
134 | |
135 | avatar.new.set({ |
136 | link: hash, |
137 | size: _data.data.length, |
138 | type: _data.mimetype, |
139 | width: 512, |
140 | height: 512 |
141 | }) |
142 | }) |
143 | } |
144 | lb.close() |
145 | }) |
146 | lb.show(el) |
147 | } |
148 | |
149 | function clearNewSelections () { |
150 | name.new.set(null) |
151 | avatar.new.set({}) |
152 | } |
153 | |
154 | function handleUpdateClick () { |
155 | const newName = name.new() |
156 | const newAvatar = avatar.new() |
157 | |
158 | const msg = { |
159 | type: 'about', |
160 | about: id |
161 | } |
162 | |
163 | if (newName) msg.name = newName |
164 | if (newAvatar.link) msg.image = newAvatar |
165 | |
166 | api.message.html.confirm(msg, (err, data) => { |
167 | if (err) return console.error(err) |
168 | |
169 | clearNewSelections() |
170 | |
171 | // TODO - update aliases displayed |
172 | }) |
173 | } |
174 | } |
175 | } |
176 | |
177 | function crop (d, cb) { |
178 | var canvas = hypercrop(h('img', {src: d})) |
179 | |
180 | return h('AboutImageEditor', [ |
181 | h('header', 'Click and drag to crop your avatar.'), |
182 | canvas, |
183 | // canvas.selection, |
184 | h('section.actions', [ |
185 | h('button.cancel', { 'ev-click': () => cb(new Error('canceled')) }, 'cancel'), |
186 | h('button.okay', { 'ev-click': () => cb(null, canvas.selection.toDataURL()) }, 'okay') |
187 | ]) |
188 | ]) |
189 | } |
190 | |
191 |
Built with git-ssb-web