Files: e39763f409d19a3fb6f6c9052dccd36946f8be25 / app / html / context.js
5722 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, computed, map, when, Dict, dictToCollection, Array: MutantArray, resolve } = require('mutant') |
3 | const pull = require('pull-stream') |
4 | const next = require('pull-next-step') |
5 | const get = require('lodash/get') |
6 | const isEmpty = require('lodash/isEmpty') |
7 | |
8 | exports.gives = nest('app.html.context') |
9 | |
10 | exports.needs = nest({ |
11 | 'about.html.avatar': 'first', |
12 | 'about.obs.name': 'first', |
13 | 'feed.pull.private': 'first', |
14 | 'feed.pull.rollup': 'first', |
15 | 'feed.pull.public': 'first', |
16 | 'keys.sync.id': 'first', |
17 | 'history.sync.push': 'first', |
18 | 'message.html.subject': 'first', |
19 | 'sbot.obs.localPeers': 'first', |
20 | 'translations.sync.strings': 'first', |
21 | 'unread.sync.isUnread': 'first' |
22 | }) |
23 | |
24 | |
25 | exports.create = (api) => { |
26 | return nest('app.html.context', (location) => { |
27 | |
28 | const strings = api.translations.sync.strings() |
29 | const myKey = api.keys.sync.id() |
30 | |
31 | var nearby = api.sbot.obs.localPeers() |
32 | var recentPeersContacted = Dict() |
33 | var recentThreads = Dict() |
34 | // TODO - extract as contact.obs.recentPrivate or something |
35 | |
36 | var m = {} |
37 | |
38 | pull( |
39 | next(api.feed.pull.private, {reverse: true, limit: 100, live: false}, ['value', 'timestamp']), |
40 | pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes? |
41 | pull.filter(msg => msg.value.content.recps), |
42 | pull.drain(msg => { |
43 | var author = msg.value.author |
44 | if(api.unread.sync.isUnread(msg)) { |
45 | var seen = author === myKey ? 0 : 1 |
46 | recentPeersContacted |
47 | .put(author, (recentPeersContacted.get(author)||0)+seen) |
48 | } |
49 | else |
50 | recentPeersContacted.put(author, 0) |
51 | }) |
52 | ) |
53 | |
54 | //TODO: calculate unread state for public threads/blogs |
55 | // pull( |
56 | // next(api.feed.pull.public, {reverse: true, limit: 100, live: false}, ['value', 'timestamp']), |
57 | // pull.drain(msg => { |
58 | // |
59 | // }) |
60 | // ) |
61 | |
62 | return h('Context -feed', [ |
63 | LevelOneContext(), |
64 | LevelTwoContext() |
65 | ]) |
66 | |
67 | function LevelOneContext () { |
68 | //the "discovery" button |
69 | const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'home'] |
70 | |
71 | return h('div.level.-one', [ |
72 | // Nearby |
73 | computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null), |
74 | map(nearby, feedId => Option({ |
75 | notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO |
76 | imageEl: api.about.html.avatar(feedId), |
77 | label: api.about.obs.name(feedId), |
78 | selected: location.feed === feedId, |
79 | location: computed(recentPeersContacted, recent => { |
80 | const lastMsg = recent[feedId] |
81 | return lastMsg |
82 | ? Object.assign(lastMsg, { feed: feedId }) |
83 | : { page: 'threadNew', feed: feedId } |
84 | }), |
85 | })), |
86 | computed(nearby, n => !isEmpty(n) ? h('hr') : null), |
87 | |
88 | // Discover |
89 | Option({ |
90 | //XXX not a random number of notifications! |
91 | notifications: Math.floor(Math.random()*5+1), |
92 | imageEl: h('i.fa.fa-binoculars'), |
93 | label: strings.blogIndex.title, |
94 | selected: PAGES_UNDER_DISCOVER.includes(location.page), |
95 | location: { page: 'blogIndex' }, |
96 | }), |
97 | |
98 | // Recent Messages |
99 | map(dictToCollection(recentPeersContacted), ({ key, value }) => { |
100 | const feedId = key() |
101 | const lastMsg = value() |
102 | if (nearby.has(feedId)) return |
103 | |
104 | return Option({ |
105 | //the number of threads with each peer |
106 | notifications: value, |
107 | //Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO |
108 | imageEl: api.about.html.avatar(feedId), |
109 | label: api.about.obs.name(feedId), |
110 | selected: location.feed === feedId, |
111 | location: Object.assign({}, lastMsg, { feed: feedId }) // TODO make obs? |
112 | }) |
113 | }) |
114 | ]) |
115 | } |
116 | |
117 | function LevelTwoContext () { |
118 | const { key, value, feed: targetUser, page } = location |
119 | const root = get(value, 'content.root', key) |
120 | if (!targetUser) return |
121 | |
122 | var threads = MutantArray() |
123 | |
124 | pull( |
125 | next(api.feed.pull.private, {reverse: true, limit: 100, live: false}, ['value', 'timestamp']), |
126 | pull.filter(msg => msg.value.content.recps), |
127 | pull.filter(msg => msg.value.content.recps |
128 | .map(recp => typeof recp === 'object' ? recp.link : recp) |
129 | .some(recp => recp === targetUser) |
130 | ), |
131 | api.feed.pull.rollup(), |
132 | pull.drain(thread => threads.push(thread)) |
133 | ) |
134 | |
135 | return h('div.level.-two', [ |
136 | Option({ |
137 | selected: page === 'threadNew', |
138 | location: {page: 'threadNew', feed: targetUser}, |
139 | label: h('Button', strings.threadNew.action.new), |
140 | }), |
141 | map(threads, thread => { |
142 | return Option({ |
143 | label: api.message.html.subject(thread), |
144 | selected: thread.key === root, |
145 | location: Object.assign(thread, { feed: targetUser }), |
146 | }) |
147 | }) |
148 | ]) |
149 | } |
150 | |
151 | function Option ({ notifications = 0, imageEl, label, location, selected }) { |
152 | const className = selected ? '-selected' : '' |
153 | const goToLocation = (e) => { |
154 | e.preventDefault() |
155 | e.stopPropagation() |
156 | api.history.sync.push(resolve(location)) |
157 | } |
158 | |
159 | if (!imageEl) { |
160 | return h('Option', { className, 'ev-click': goToLocation }, [ |
161 | h('div.label', label) |
162 | ]) |
163 | } |
164 | |
165 | return h('Option', { className }, [ |
166 | h('div.circle', [ |
167 | when(notifications, h('div.alert', notifications)), |
168 | imageEl |
169 | ]), |
170 | h('div.label', { 'ev-click': goToLocation }, label) |
171 | ]) |
172 | } |
173 | }) |
174 | } |
175 | |
176 | |
177 | |
178 | |
179 | |
180 |
Built with git-ssb-web