git ssb

2+

mixmix / ticktack



Tree: 587259f06bdc6df8de5db46b20722a8402b644dd

Files: 587259f06bdc6df8de5db46b20722a8402b644dd / app / html / context.js

7562 bytesRaw
1const nest = require('depnest')
2const { h, computed, map, when, Dict, dictToCollection, Array: MutantArray, Value, resolve } = require('mutant')
3const pull = require('pull-stream')
4const next = require('pull-next-step')
5const get = require('lodash/get')
6const isEmpty = require('lodash/isEmpty')
7
8exports.gives = nest('app.html.context')
9
10exports.needs = nest({
11 'app.html.scroller': 'first',
12 'about.html.avatar': 'first',
13 'about.obs.name': 'first',
14 'feed.pull.private': 'first',
15 'feed.pull.rollup': '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})
22
23exports.create = (api) => {
24 var recentMsgCache = MutantArray()
25 var usersMsgCache = Dict() // { id: [ msgs ] }
26
27 return nest('app.html.context', context)
28
29 function context (location) {
30 const strings = api.translations.sync.strings()
31 const myKey = api.keys.sync.id()
32
33 var nearby = api.sbot.obs.localPeers()
34
35 return h('Context -feed', [
36 LevelOneContext(),
37 LevelTwoContext()
38 ])
39
40 function LevelOneContext () {
41 function isDiscoverContext (loc) {
42 const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'home']
43
44 return PAGES_UNDER_DISCOVER.includes(location.page)
45 || get(location, 'value.private') === undefined
46 }
47
48 const prepend = [
49 // Nearby
50 computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null),
51 map(nearby, feedId => Option({
52 notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO
53 imageEl: api.about.html.avatar(feedId, 'small'),
54 label: api.about.obs.name(feedId),
55 selected: location.feed === feedId,
56 location: computed(recentMsgCache, recent => {
57 const lastMsg = recent.find(msg => msg.value.author === feedId)
58 return lastMsg
59 ? Object.assign(lastMsg, { feed: feedId })
60 : { page: 'threadNew', feed: feedId }
61 }),
62 }), { comparer: (a, b) => a === b }),
63
64 // ---------------------
65 computed(nearby, n => !isEmpty(n) ? h('hr') : null),
66
67 // Discover
68 Option({
69 notifications: Math.floor(Math.random()*5+1),
70 imageEl: h('i.fa.fa-binoculars'),
71 label: strings.blogIndex.title,
72 selected: isDiscoverContext(location),
73 location: { page: 'blogIndex' },
74 })
75 ]
76
77 return api.app.html.scroller({
78 classList: [ 'level', '-one' ],
79 prepend,
80 stream: api.feed.pull.private,
81 filter: () => pull(
82 pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes?
83 pull.filter(msg => msg.value.author != myKey),
84 pull.filter(msg => msg.value.content.recps)
85 ),
86 store: recentMsgCache,
87 updateTop: updateRecentMsgCache,
88 updateBottom: updateRecentMsgCache,
89 render: (msgObs) => {
90 const msg = resolve(msgObs)
91 const { author } = msg.value
92 if (nearby.has(author)) return
93
94 return Option({
95 notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO
96 imageEl: api.about.html.avatar(author),
97 label: api.about.obs.name(author),
98 selected: location.feed === author,
99 location: Object.assign({}, msg, { feed: author }) // TODO make obs?
100 })
101 }
102 })
103
104 function updateRecentMsgCache (soFar, newMsg) {
105 soFar.transaction(() => {
106 const { author, timestamp } = newMsg.value
107 const index = indexOf(soFar, (msg) => author === resolve(msg).value.author)
108 var object = Value()
109
110 if (index >= 0) {
111 // reference already exists, lets use this instead!
112 const existingMsg = soFar.get(index)
113
114 if (resolve(existingMsg).value.timestamp > timestamp) return
115 // but abort if the existing reference is newer
116
117 object = existingMsg
118 soFar.deleteAt(index)
119 }
120
121 object.set(newMsg)
122
123 const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
124 if (justOlderPosition > -1) {
125 soFar.insert(object, justOlderPosition)
126 } else {
127 soFar.push(object)
128 }
129 })
130 }
131
132 }
133
134 function LevelTwoContext () {
135 const { key, value, feed: targetUser, page } = location
136 const root = get(value, 'content.root', key)
137 if (!targetUser) return
138
139
140 const prepend = Option({
141 selected: page === 'threadNew',
142 location: {page: 'threadNew', feed: targetUser},
143 label: h('Button', strings.threadNew.action.new),
144 })
145
146 var userMsgCache = usersMsgCache.get(targetUser)
147 if (!userMsgCache) {
148 userMsgCache = MutantArray()
149 usersMsgCache.put(targetUser, userMsgCache)
150 }
151
152 return api.app.html.scroller({
153 classList: [ 'level', '-two' ],
154 prepend,
155 stream: api.feed.pull.private,
156 filter: () => pull(
157 pull.filter(msg => !msg.value.content.root), // only show the root message??? - check this still works with lastmessage
158 pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes?
159 pull.filter(msg => msg.value.content.recps),
160 pull.filter(msg => msg.value.content.recps
161 .map(recp => typeof recp === 'object' ? recp.link : recp)
162 .some(recp => recp === targetUser)
163 )
164 ),
165 store: userMsgCache,
166 updateTop: updateUserMsgCache,
167 updateBottom: updateUserMsgCache,
168 render: (rootMsgObs) => {
169 const rootMsg = resolve(rootMsgObs)
170 return Option({
171 label: api.message.html.subject(rootMsg),
172 selected: rootMsg.key === root,
173 location: Object.assign(rootMsg, { feed: targetUser }),
174 })
175 }
176 })
177
178 function updateUserMsgCache (soFar, newMsg) {
179 soFar.transaction(() => {
180 const { timestamp } = newMsg.value
181 const index = indexOf(soFar, (msg) => timestamp === resolve(msg).value.timestamp)
182
183 if (index >= 0) return
184 // if reference already exists, abort
185
186 var object = Value(newMsg)
187
188 const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
189 if (justOlderPosition > -1) {
190 soFar.insert(object, justOlderPosition)
191 } else {
192 soFar.push(object)
193 }
194 })
195 }
196 }
197
198 function Option ({ notifications = 0, imageEl, label, location, selected }) {
199 const className = selected ? '-selected' : ''
200 const goToLocation = (e) => {
201 e.preventDefault()
202 e.stopPropagation()
203 api.history.sync.push(resolve(location))
204 }
205
206 if (!imageEl) {
207 return h('Option', { className, 'ev-click': goToLocation }, [
208 h('div.label', label)
209 ])
210 }
211
212 return h('Option', { className }, [
213 h('div.circle', [
214 when(notifications, h('div.alert', notifications)),
215 imageEl
216 ]),
217 h('div.label', { 'ev-click': goToLocation }, label)
218 ])
219 }
220 }
221}
222
223function indexOf (array, fn) {
224 for (var i = 0; i < array.getLength(); i++) {
225 if (fn(array.get(i))) {
226 return i
227 }
228 }
229 return -1
230}
231

Built with git-ssb-web