git ssb

16+

Dominic / patchbay



Commit 80f4b86c51b90a08b5bad555e074dd466fe1ecd6

Merge branch 'refactor_profile' into master

Dominic Tarr committed on 1/26/2017, 5:14:54 PM
Parent: 43df95c4d63b7a5a52000171ed8ae86b874f3b5a
Parent: 3b0cb72e5d99d144b36ae7497086b1cc81b13384

Files changed

modules_basic/avatar/edit.jschanged
modules_basic/avatar/profile.jschanged
modules_basic/avatar/edit.mcssadded
modules_basic/avatar/profile.mcssadded
modules_basic/feed.jschanged
modules_basic/follow.jschanged
modules_basic/follow.mcssadded
modules_core/style/mixins.jschanged
modules_basic/avatar/edit.jsView
@@ -1,31 +1,32 @@
11 'use strict'
2-var dataurl = require('dataurl-')
3-var hyperfile = require('hyperfile')
4-var hypercrop = require('hypercrop')
5-var hyperlightbox = require('hyperlightbox')
6-var h = require('hyperscript')
7-var pull = require('pull-stream')
8-var getAvatar = require('ssb-avatar')
9-var ref = require('ssb-ref')
10-var visualize = require('visualize-buffer')
11-var self_id = require('../../keys').id
2 +const fs = require('fs')
3 +const dataurl = require('dataurl-')
4 +const hyperfile = require('hyperfile')
5 +const hypercrop = require('hypercrop')
6 +const hyperlightbox = require('hyperlightbox')
7 +const h = require('../../h')
8 +const {
9 + Value, Array: MutantArray, Dict: MutantObject, Struct,
10 + map, computed, when, dictToCollection
11 +} = require('@mmckegg/mutant')
12 +const pull = require('pull-stream')
13 +const getAvatar = require('ssb-avatar')
14 +const ref = require('ssb-ref')
15 +const visualize = require('visualize-buffer')
16 +const self_id = require('../../keys').id
1217
1318 function crop (d, cb) {
1419 var canvas = hypercrop(h('img', {src: d}))
1520
16- return h('div.column.avatar_pic',
21 + return h('div.column.avatar_pic', [
1722 canvas,
1823 //canvas.selection,
19- h('div.row.avatar_pic__controls',
20- h('button', 'okay', {onclick: function () {
21- cb(null, canvas.selection.toDataURL())
22- }}),
23- h('button', 'cancel', {onclick: function () {
24- cb(new Error('canceled'))
25- }})
26- )
27- )
24 + h('div.row.avatar_pic__controls', [
25 + h('button', {'ev-click': () => cb(null, canvas.selection.toDataURL()) }, 'okay'),
26 + h('button', {'ev-click': () => cb(new Error('canceled')) }, 'cancel')
27 + ])
28 + ])
2829 }
2930
3031 exports.needs = {
3132 message_confirm: 'first',
@@ -34,110 +35,164 @@
3435 sbot_links: 'first',
3536 avatar_name: 'first'
3637 }
3738
38-exports.gives = 'avatar_edit'
39 +exports.gives = {
40 + avatar_edit: true,
41 + mcss: true
42 +}
3943
4044 exports.create = function (api) {
41- return function (id) {
45 + return {
46 + avatar_edit,
47 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
48 + }
4249
43- var img = visualize(new Buffer(id.substring(1), 'base64'), 256)
44- img.classList.add('avatar--large')
50 + function avatar_edit (id) {
4551
46- var lb = hyperlightbox()
47- var name_input = h('input', {placeholder: 'rename'})
48- var name = api.avatar_name(id)
49- var selected = null
52 + var avatar = Struct({
53 + original: Value(visualize(new Buffer(id.substring(1), 'base64'), 256).src),
54 + new: MutantObject()
55 + })
5056
51- getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) {
57 + getAvatar({links: api.sbot_links}, self_id, id, (err, _avatar) => {
5258 if (err) return console.error(err)
5359 //don't show user has already selected an avatar.
54- if(selected) return
55- if(ref.isBlob(avatar.image))
56- img.src = api.blob_url(avatar.image)
60 + if(ref.isBlob(_avatar.image))
61 + avatar.original.set(api.blob_url(_avatar.image))
5762 })
5863
59- var also_pictured = h('div.profile__alsopicturedas.wrap')
64 + var name = Struct({
65 + original: Value(api.avatar_name(id)),
66 + new: Value()
67 + })
6068
69 + var images = MutantArray()
6170 pull(
6271 api.sbot_links({dest: id, rel: 'about', values: true}),
63- pull.map(function (e) {
64- return e.value.content.image
65- }),
66- pull.filter(function (e) {
67- return e && 'string' == typeof e.link
68- }),
72 + pull.map(e => e.value.content.image),
73 + pull.filter(e => e && 'string' == typeof e.link),
6974 pull.unique('link'),
70- pull.drain(function (image) {
71- also_pictured.appendChild(
72- h('a', {href:'#', onclick: function (ev) {
73- ev.stopPropagation()
74- ev.preventDefault()
75- selected = image
76- img.src = api.blob_url(image.link || image)
77- }},
78- h('img.avatar--thumbnail', {src: api.blob_url(image)})
79- )
80- )
75 + pull.drain(image => images.push(image) )
76 + )
77 +
78 + var namesRecord = MutantObject()
79 + pull(
80 + api.sbot_links({dest: id, rel: 'about', values: true}),
81 + pull.map(e => e.value.content.name),
82 + pull.filter(Boolean),
83 + pull.drain(name => {
84 + var n = namesRecord.get(name) || 0
85 + namesRecord.put(name, n+1)
8186 })
8287 )
88 + var names = dictToCollection(namesRecord)
8389
84- return h('div.row.profile',
85- lb,
86- img,
87- h('div.column.profile__info',
88- h('strong', name),
89- name_input,
90 + var lb = hyperlightbox()
91 +
92 +
93 + var description = '' //TODO load this in, make this editable
9094
91- hyperfile.asDataURL(function (data) {
92- var el = crop(data, function (err, data) {
93- if(data) {
94- img.src = data
95- var _data = dataurl.parse(data)
96- pull(
97- pull.once(_data.data),
98- api.sbot_blobs_add(function (err, hash) {
99- //TODO. Alerts are EVIL.
100- //I use them only in a moment of weakness.
95 + var isPossibleUpdate = computed([name.new, avatar.new], (name, avatar) => {
96 + return name || avatar.link
97 + })
10198
102- if(err) return alert(err.stack)
103- selected = {
104- link: hash,
105- size: _data.data.length,
106- type: _data.mimetype,
107- width: 512,
108- height: 512
109- }
99 + var avatarSrc = computed([avatar], avatar => {
100 + if (avatar.new.link) return api.blob_url(avatar.new.link)
101 + else return avatar.original
102 + })
110103
111- })
112- )
113- }
114- lb.close()
115- })
116- lb.show(el)
117- }),
118- h('button', 'update', {onclick: function () {
119- if(name_input.value)
120- name.textContent = name_input.value
104 + var displayedName = computed([name], name => {
105 + if (name.new) return '@'+name.new
106 + else return name.original
107 + })
121108
122- if(selected)
123- api.message_confirm({
124- type: 'about',
125- about: id,
126- name: name_input.value || undefined,
127- image: selected
109 + return h('ProfileEdit', [
110 + h('section.lightbox', lb),
111 + h('section.avatar', [
112 + h('section', [
113 + h('img', { src: avatarSrc }),
114 + ]),
115 + h('footer', displayedName),
116 + ]),
117 + h('section.description', description),
118 + h('section.aliases', [
119 + h('header', 'Aliases'),
120 + h('section.avatars', [
121 + h('header', 'Avatars'),
122 + map(images, image => h('img', {
123 + 'src': api.blob_url(image),
124 + 'ev-click': () => avatar.new.set(image)
125 + })),
126 + h('div.file-upload', [
127 + hyperfile.asDataURL(dataUrlCallback)
128 + ])
129 + ]),
130 + h('section.names', [
131 + h('header', 'Names'),
132 + h('section', [
133 + map(names, n => h('div', { 'ev-click': () => name.new.set(n.key()) }, [
134 + h('div.name', n.key),
135 + h('div.count', n.value)
136 + ])),
137 + h('input', {
138 + placeholder: ' + another name',
139 + 'ev-keyup': e => name.new.set(e.target.value)
128140 })
129- else if(name_input.value) //name only
130- api.message_confirm({
131- type: 'about',
132- about: id,
133- name: name_input.value || undefined,
141 + ])
142 + ]),
143 + when(isPossibleUpdate, h('section.action', [
144 + h('button.cancel', { 'ev-click': handleCancelClick }, 'cancel'),
145 + h('button.confirm', { 'ev-click': handleUpdateClick }, 'confirm changes')
146 + ]))
147 + ])
148 + ])
149 +
150 + function dataUrlCallback (data) {
151 + var el = crop(data, (err, data) => {
152 + if(data) {
153 + var _data = dataurl.parse(data)
154 + pull(
155 + pull.once(_data.data),
156 + api.sbot_blobs_add((err, hash) => {
157 + //TODO. Alerts are EVIL.
158 + //I use them only in a moment of weakness.
159 +
160 + if(err) return alert(err.stack)
161 + avatar.new.set({
162 + link: hash,
163 + size: _data.data.length,
164 + type: _data.mimetype,
165 + width: 512,
166 + height: 512
167 + })
134168 })
135- else
136- //another moment of weakness
137- alert('must select a name or image')
138- }}),
139- also_pictured
140- )
141- )
169 + )
170 + }
171 + lb.close()
172 + })
173 + lb.show(el)
174 + }
175 +
176 + function handleCancelClick () {
177 + name.new.set(null)
178 + avatar.new.set({})
179 + }
180 +
181 + function handleUpdateClick () {
182 + const newName = name.new()
183 + const newAvatar = avatar.new()
184 +
185 + const msg = {
186 + type: 'about',
187 + about: id
188 + }
189 +
190 + if (newName) msg.name = newName
191 + if (newAvatar.link) msg.image = newAvatar
192 +
193 + api.message_confirm(msg)
194 + }
142195 }
196 +
143197 }
198 +
modules_basic/avatar/profile.jsView
@@ -1,79 +1,86 @@
1-var h = require('hyperscript')
2-var pull = require('pull-stream')
1 +const fs = require('fs')
2 +const h = require('../../h')
3 +const pull = require('pull-stream')
4 +const { unique, drain } = pull
5 +const {
6 + Array: MutantArray,
7 + map, computed, when, dictToCollection
8 +} = require('@mmckegg/mutant')
39
10 +
411 exports.needs = {
512 avatar_image_link: 'first',
613 avatar_action: 'map',
714 avatar_edit: 'first',
815 follows: 'first',
916 followers: 'first'
1017 }
1118
12-exports.gives = 'avatar_profile'
13-
14-function streamToList(stream, el) {
15- pull(
16- stream,
17- pull.drain(function (item) {
18- if(item) el.appendChild(item)
19- })
20- )
21- return el
19 +exports.gives = {
20 + avatar_profile: true,
21 + mcss: true
2222 }
2323
2424 exports.create = function (api) {
25-
26- function image_link (id) {
27- return api.avatar_image_link(id, 'thumbnail')
25 + return {
26 + avatar_profile,
27 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
2828 }
2929
30- return function (id) {
30 + function avatar_profile (id) {
3131
32- var follows_el = h('div.profile__follows.wrap')
33- var friends_el = h('div.profile__friendss.wrap')
34- var followers_el = h('div.profile__followers.wrap')
35- var a, b
32 + var rawFollows = MutantArray()
33 + var rawFollowers = MutantArray()
34 + var friends = computed([rawFollows, rawFollowers], (follows, followers) => {
35 + return follows.filter(follow => followers.includes(follow))
36 + })
3637
37- pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) {
38- a = ary || []; next()
39- }))
40- pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) {
41- b = ary || {}; next()
42- }))
38 + var follows = computed([rawFollows, friends], (follows, friends) => {
39 + return follows.filter(follow => !friends.includes(follow))
40 + })
41 + var followers = computed([rawFollowers, friends], (followers, friends) => {
42 + return followers.filter(follower => !friends.includes(follower))
43 + })
4344
44- function next () {
45- if(!(a && b)) return
46- var _c = [], _a = [], _b = []
47-
48- a.forEach(function (id) {
49- if(!~b.indexOf(id)) _a.push(id)
50- else _c.push(id)
51- })
52- b.forEach(function (id) {
53- if(!~_c.indexOf(id)) _b.push(id)
54- })
55- function add (ary, el) {
56- ary.forEach(function (id) { el.appendChild(image_link(id)) })
57- }
58-
59- add(_a, follows_el)
60- add(_c, friends_el)
61- add(_b, followers_el)
62- }
63-
64-
65- return h('div.column.profile',
66- api.avatar_edit(id),
67- api.avatar_action(id),
68- h('div.profile__relationships.column',
69- h('strong', 'follows'),
70- follows_el,
71- h('strong', 'friends'),
72- friends_el,
73- h('strong', 'followers'),
74- followers_el
45 + pull(
46 + api.follows(id),
47 + unique(),
48 + drain(
49 + peer => rawFollows.push(peer),
50 + (err, data) => console.log('follows drain done', err, data)
7551 )
7652 )
53 + pull(
54 + api.followers(id),
55 + unique(),
56 + drain(
57 + peer => rawFollowers.push(peer),
58 + (err, data) => console.log('followers drain done', err, data)
59 + )
60 + )
61 +
62 + return h('Profile', [
63 + h('section.edit', api.avatar_edit(id)),
64 + h('section.relationships', [
65 + h('header', 'Relationships'),
66 + h('div.your-status', [
67 + h('header', 'Your status'),
68 + h('section.action', api.avatar_action(id))
69 + ]),
70 + h('div.friends', [
71 + h('header', 'Friends'),
72 + h('section', map(friends, id => api.avatar_image_link(id)))
73 + ]),
74 + h('div.follows', [
75 + h('header', 'Follows'),
76 + h('section', map(follows, id => api.avatar_image_link(id)))
77 + ]),
78 + h('div.followers', [
79 + h('header', 'Followers'),
80 + h('section', map(followers, id => api.avatar_image_link(id)))
81 + ])
82 + ])
83 + ])
7784 }
7885
7986 }
modules_basic/avatar/edit.mcssView
@@ -1,0 +1,161 @@
1 +ProfileEdit {
2 + display: flex
3 + flex-wrap: wrap
4 + justify-content: space-between
5 +
6 + margin-bottom: 2rem
7 +
8 + section.lightbox {
9 + position: absolute
10 + }
11 +
12 + section.avatar {
13 + margin-right: 1rem
14 +
15 + section img {
16 + width: 256px
17 + height: 256px
18 + }
19 +
20 + footer {
21 + font-size: 1.2rem
22 + }
23 + }
24 +
25 + section.description {
26 + flex-basis: 40%
27 + flex-grow: 1
28 +
29 + margin-top: 1rem
30 + }
31 +
32 + section.aliases {
33 + flex-basis: 100%
34 +
35 + margin-top: 1rem
36 +
37 + header {
38 + margin-bottom: .8rem
39 + border-bottom: 1px gainsboro solid
40 + }
41 +
42 + section {
43 + display: flex
44 + flex-wrap: wrap
45 + align-content: flex-start
46 +
47 + margin-bottom: 1rem
48 +
49 + header {
50 + flex-basis: 100%
51 +
52 + font-size: .9rem
53 + $textSubtle
54 +
55 + margin-bottom: .2rem
56 + }
57 +
58 + input {
59 + }
60 + }
61 +
62 + section.avatars {
63 + img {
64 + $avatar-large
65 + margin: 0 .15rem 0.2rem 0
66 +
67 + cursor: pointer
68 + }
69 +
70 + div.file-upload {
71 + position: relative
72 +
73 + input[type="file"] {
74 + $avatar-large
75 + color: transparent
76 +
77 + ::-webkit-file-upload-button {
78 + visibility: hidden
79 + }
80 +
81 + ::before {
82 + position: absolute
83 +
84 + background: #fff
85 + color: #666
86 + border: 1px solid #bbb
87 + border-radius: .2rem
88 + padding: .5rem
89 + cursor: pointer
90 +
91 + margin: 0
92 + padding: 10% 20%
93 + top: 12%
94 + left: 18%
95 +
96 + content: '+'
97 + font-size: 1.4rem
98 +
99 + outline: none
100 + white-space: nowrap
101 + -webkit-user-select: none
102 + }
103 +
104 + :active, :focus {
105 + outline: none
106 + box-shadow: none
107 + }
108 + }
109 + }
110 + }
111 +
112 + section.names {
113 + header {
114 + }
115 +
116 + section {
117 + display: flex
118 + flex-wrap: wrap
119 +
120 + div {
121 + display: flex
122 + cursor: pointer
123 +
124 + border: 1px gainsboro solid
125 + margin: 0 .4rem .5rem 0
126 +
127 + div { padding: .3rem }
128 +
129 + div.name {
130 + border-right: 1px gainsboro solid
131 + }
132 +
133 + div.count {
134 + font-size: .9rem
135 + background-color: #eeeeee
136 + }
137 + }
138 +
139 + input {
140 + border: 1px gainsboro solid
141 + font-size: 1rem
142 + height: 1.7rem
143 + }
144 + }
145 +
146 + }
147 +
148 + section.action {
149 + button.cancel {
150 + margin-left: 0
151 + }
152 +
153 + button.confirm {
154 + color: #fff
155 + $backgroundPrimary
156 + border: none
157 + }
158 + }
159 + }
160 +}
161 +
modules_basic/avatar/profile.mcssView
@@ -1,0 +1,71 @@
1 +Profile {
2 +
3 + section.edit {
4 +
5 + }
6 +
7 + section.relationships {
8 + header {
9 + margin-bottom: .8rem
10 + border-bottom: 1px gainsboro solid
11 + }
12 +
13 + div {
14 + display: flex
15 + flex-wrap: wrap
16 + justify-content: space-between
17 + align-content: flex-start
18 +
19 + min-height: 5rem
20 + margin-bottom: 2rem
21 +
22 + header {
23 + flex-basis: 100%
24 +
25 + $textSubtle
26 + font-size: .9rem
27 +
28 + margin-bottom: .2rem
29 + }
30 +
31 + section a {
32 + margin-right: .2rem
33 +
34 + img { $avatar-small }
35 + }
36 + }
37 +
38 + div.your-status {
39 + margin: 0
40 + section.action {
41 + }
42 + }
43 +
44 + div.friends {
45 + section a {
46 + margin: 0 .2rem 0.2rem 0
47 +
48 + img {
49 + $avatar-large
50 + }
51 + }
52 + }
53 +
54 + div.follows {
55 + }
56 +
57 + div.followers {
58 + }
59 + }
60 +}
61 +
62 +$avatar-large {
63 + width: 56px
64 + height: 56px
65 +}
66 +
67 +$avatar-small {
68 + width: 32px
69 + height: 32px
70 +}
71 +
modules_basic/feed.jsView
@@ -25,8 +25,9 @@
2525 var div = h('div.column.scroller',
2626 {style: {'overflow':'auto'}},
2727 h('div.scroller__wrapper',
2828 h('div', api.avatar_profile(id)),
29 + h('header', 'Activity'),
2930 content
3031 )
3132 )
3233
@@ -46,9 +47,9 @@
4647 u.next(api.sbot_user_feed, {
4748 id: id, reverse: true,
4849 limit: 50, live: false
4950 }, ['value', 'sequence']),
50- pull.through(console.log.bind(console)),
51 + // pull.through(console.log.bind(console)),
5152 Scroller(div, content, api.message_render, false, false)
5253 )
5354
5455 return div
modules_basic/follow.jsView
@@ -1,5 +1,6 @@
1-var h = require('hyperscript')
1 +const fs = require('fs')
2 +const h = require('../h')
23
34 //render a message when someone follows someone,
45 //so you see new users
56 function isRelated(value, name) {
@@ -17,74 +18,92 @@
1718 exports.gives = {
1819 message_content: true,
1920 message_content_mini: true,
2021 avatar_action: true,
22 + mcss: true
2123 }
2224
2325 exports.create = function (api) {
24- var exports = {}
25- exports.message_content =
26- exports.message_content_mini = function (msg) {
27- var content = msg.value.content
28- if(content.type == 'contact' && content.contact) {
29- var relation = isRelated(content.following, 'follows')
30- if(content.blocking) relation = 'blocks'
26 + return {
27 + message_content_mini,
28 + message_content,
29 + avatar_action,
30 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
31 + }
32 +
33 + function message_content_mini (msg) {
34 + const { type, contact, following, blocking } = msg.value.content
35 + if(type == 'contact' && contact) {
36 + var relation = isRelated(following, 'follows')
37 + if(blocking) relation = 'blocks'
3138 return [
32- relation, ' ',
33- api.avatar_link(content.contact, api.avatar_name(content.contact), '')
39 + relation,
40 + ' ',
41 + api.avatar_link(contact, api.avatar_name(contact), '')
3442 ]
3543 }
3644 }
3745
38- exports.message_content = function (msg) {
39-
40- var content = msg.value.content
41- if(content.type == 'contact' && content.contact) {
42- var relation = isRelated(content.following, 'follows')
43- if(content.blocking) relation = 'blocks'
44- return h('div.contact', relation, api.avatar(msg.value.content.contact, 'thumbnail'))
46 + function message_content (msg) {
47 + const { type, contact, following, blocking } = msg.value.content
48 + if(type == 'contact' && contact) {
49 + var relation = isRelated(following, 'follows')
50 + if(blocking) relation = 'blocks'
51 + return h('div.contact', [
52 + relation,
53 + api.avatar(contact, 'thumbnail')
54 + ])
4555 }
4656 }
4757
48- exports.avatar_action = function (id) {
58 + function avatar_action (id) {
4959 var follows_you, you_follow
5060
5161 var self_id = require('../keys').id
52- api.follower_of(self_id, id, function (err, f) {
62 + api.follower_of(self_id, id, (err, f) => {
5363 you_follow = f
5464 update()
5565 })
56- api.follower_of(id, self_id, function (err, f) {
66 + api.follower_of(id, self_id, (err, f) => {
5767 follows_you = f
5868 update()
5969 })
6070
61- var state = h('label')
62- var label = h('span')
71 + var followBtn = h('button', { 'ev-click': toggleFollow }, 'loading')
72 + var state = h('label', 'loading')
6373
6474 function update () {
6575 state.textContent = (
66- follows_you && you_follow ? 'friend'
67- : follows_you ? 'follows you'
68- : you_follow ? 'you follow'
76 + follows_you && you_follow ? '- you are friends'
77 + : follows_you ? '- they follow you'
78 + : you_follow ? '- you are following'
6979 : ''
7080 )
81 +
82 + if (you_follow === undefined) return
83 + followBtn.textContent = you_follow ? 'unfollow' : 'follow'
84 + }
7185
72- label.textContent = you_follow ? 'unfollow' : 'follow'
86 + return h('Follow', [
87 + followBtn,
88 + state
89 + ])
90 +
91 + function toggleFollow () {
92 + if (followBtn.textContent === 'loading') return
93 + const msg = {
94 + type: 'contact',
95 + contact: id,
96 + following: !you_follow
97 + }
98 +
99 + api.message_confirm(msg, (err, msg) => {
100 + if (err) return console.error(err)
101 +
102 + you_follow = msg.value.content.following
103 + update()
104 + })
73105 }
74-
75- return h('div', state,
76- h('a', {href:'#', onclick: function () {
77- api.message_confirm({
78- type: 'contact',
79- contact: id,
80- following: !you_follow
81- }, function (err, msg) {
82- if (err) return console.error(err)
83- you_follow = msg.value.content.following
84- update()
85- })
86- }}, h('br'), label)
87- )
106 +
88107 }
89108 return exports
90109 }
modules_basic/follow.mcssView
@@ -1,0 +1,11 @@
1 +Follow {
2 + button {
3 + width: 6rem
4 + margin: 0 .5rem 0 0
5 + }
6 +
7 + label {
8 + $textSubtle
9 + }
10 +}
11 +
modules_core/style/mixins.jsView
@@ -6,8 +6,12 @@
66
77 $textSubtle {
88 color: gray
99 }
10 +
11 + $backgroundPrimary {
12 + background-color: #50aadf
13 + }
1014 `
1115
1216 module.exports = {
1317 gives: {

Built with git-ssb-web