Commit 587259f06bdc6df8de5db46b20722a8402b644dd
Merge pull request #50 from ticktackim/cache_scroller
Cache scrollersmix irving authored on 11/21/2017, 5:22:04 AM
GitHub committed on 11/21/2017, 5:22:04 AM
Parent: 88efa57edc6f7800ae827d2a28a7aab3065c1159
Parent: 8abd634c8aeca8b334b166fabf63622e73d71996
Files changed
app/html/context.js | changed |
app/html/context.mcss | changed |
app/html/scroller.js | changed |
package-lock.json | changed |
package.json | changed |
app/html/context.js | ||
---|---|---|
@@ -1,6 +1,6 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | -const { h, computed, map, when, Dict, dictToCollection, Array: MutantArray, resolve } = require('mutant') | |
2 | +const { h, computed, map, when, Dict, dictToCollection, Array: MutantArray, Value, resolve } = require('mutant') | |
3 | 3 | const pull = require('pull-stream') |
4 | 4 | const next = require('pull-next-step') |
5 | 5 | const get = require('lodash/get') |
6 | 6 | const isEmpty = require('lodash/isEmpty') |
@@ -19,35 +19,20 @@ | ||
19 | 19 | 'sbot.obs.localPeers': 'first', |
20 | 20 | 'translations.sync.strings': 'first', |
21 | 21 | }) |
22 | 22 | |
23 | - | |
24 | 23 | exports.create = (api) => { |
25 | - return nest('app.html.context', (location) => { | |
24 | + var recentMsgCache = MutantArray() | |
25 | + var usersMsgCache = Dict() // { id: [ msgs ] } | |
26 | 26 | |
27 | + return nest('app.html.context', context) | |
28 | + | |
29 | + function context (location) { | |
27 | 30 | const strings = api.translations.sync.strings() |
28 | 31 | const myKey = api.keys.sync.id() |
29 | 32 | |
30 | 33 | var nearby = api.sbot.obs.localPeers() |
31 | - var recentMsgLog = Dict () | |
32 | - function updateRecentMsgLog (msg) { | |
33 | - const { author, timestamp } = msg.value | |
34 | 34 | |
35 | - if (!recentMsgLog.has(author)) { | |
36 | - recentMsgLog.put(author, msg) | |
37 | - return | |
38 | - } | |
39 | - | |
40 | - const currentWinner = recentMsgLog.get(author) | |
41 | - if (timestamp > currentWinner.value.timestamp) { | |
42 | - recentMsgLog.put(author, msg) | |
43 | - } | |
44 | - } | |
45 | - function isLatestMsg (msg) { | |
46 | - const { author, timestamp } = msg.value | |
47 | - return recentMsgLog.get(author).value.timestamp === timestamp | |
48 | - } | |
49 | - | |
50 | 35 | return h('Context -feed', [ |
51 | 36 | LevelOneContext(), |
52 | 37 | LevelTwoContext() |
53 | 38 | ]) |
@@ -67,10 +52,10 @@ | ||
67 | 52 | notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO |
68 | 53 | imageEl: api.about.html.avatar(feedId, 'small'), |
69 | 54 | label: api.about.obs.name(feedId), |
70 | 55 | selected: location.feed === feedId, |
71 | - location: computed(recentMsgLog, recent => { | |
72 | - const lastMsg = recent[feedId] | |
56 | + location: computed(recentMsgCache, recent => { | |
57 | + const lastMsg = recent.find(msg => msg.value.author === feedId) | |
73 | 58 | return lastMsg |
74 | 59 | ? Object.assign(lastMsg, { feed: feedId }) |
75 | 60 | : { page: 'threadNew', feed: feedId } |
76 | 61 | }), |
@@ -95,14 +80,15 @@ | ||
95 | 80 | stream: api.feed.pull.private, |
96 | 81 | filter: () => pull( |
97 | 82 | pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes? |
98 | 83 | pull.filter(msg => msg.value.author != myKey), |
99 | - pull.filter(msg => msg.value.content.recps), | |
100 | - pull.through(updateRecentMsgLog), | |
101 | - pull.filter(isLatestMsg) | |
102 | - //pull.through( // trim exisiting from content up Top case) // do this with new updateTop in mutant-scroll | |
84 | + pull.filter(msg => msg.value.content.recps) | |
103 | 85 | ), |
104 | - render: (msg) => { | |
86 | + store: recentMsgCache, | |
87 | + updateTop: updateRecentMsgCache, | |
88 | + updateBottom: updateRecentMsgCache, | |
89 | + render: (msgObs) => { | |
90 | + const msg = resolve(msgObs) | |
105 | 91 | const { author } = msg.value |
106 | 92 | if (nearby.has(author)) return |
107 | 93 | |
108 | 94 | return Option({ |
@@ -113,43 +99,101 @@ | ||
113 | 99 | location: Object.assign({}, msg, { feed: author }) // TODO make obs? |
114 | 100 | }) |
115 | 101 | } |
116 | 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 | + | |
117 | 132 | } |
118 | 133 | |
119 | 134 | function LevelTwoContext () { |
120 | 135 | const { key, value, feed: targetUser, page } = location |
121 | 136 | const root = get(value, 'content.root', key) |
122 | 137 | if (!targetUser) return |
123 | 138 | |
124 | - var threads = MutantArray() | |
125 | 139 | |
126 | 140 | const prepend = Option({ |
127 | 141 | selected: page === 'threadNew', |
128 | 142 | location: {page: 'threadNew', feed: targetUser}, |
129 | 143 | label: h('Button', strings.threadNew.action.new), |
130 | 144 | }) |
131 | 145 | |
146 | + var userMsgCache = usersMsgCache.get(targetUser) | |
147 | + if (!userMsgCache) { | |
148 | + userMsgCache = MutantArray() | |
149 | + usersMsgCache.put(targetUser, userMsgCache) | |
150 | + } | |
151 | + | |
132 | 152 | return api.app.html.scroller({ |
133 | 153 | classList: [ 'level', '-two' ], |
134 | 154 | prepend, |
135 | 155 | stream: api.feed.pull.private, |
136 | 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? | |
137 | 159 | pull.filter(msg => msg.value.content.recps), |
138 | 160 | pull.filter(msg => msg.value.content.recps |
139 | 161 | .map(recp => typeof recp === 'object' ? recp.link : recp) |
140 | 162 | .some(recp => recp === targetUser) |
141 | - ), | |
142 | - api.feed.pull.rollup() // TODO - not technically a filter ...? | |
163 | + ) | |
143 | 164 | ), |
144 | - render: (thread) => { | |
165 | + store: userMsgCache, | |
166 | + updateTop: updateUserMsgCache, | |
167 | + updateBottom: updateUserMsgCache, | |
168 | + render: (rootMsgObs) => { | |
169 | + const rootMsg = resolve(rootMsgObs) | |
145 | 170 | return Option({ |
146 | - label: api.message.html.subject(thread), | |
147 | - selected: thread.key === root, | |
148 | - location: Object.assign(thread, { feed: targetUser }), | |
171 | + label: api.message.html.subject(rootMsg), | |
172 | + selected: rootMsg.key === root, | |
173 | + location: Object.assign(rootMsg, { feed: targetUser }), | |
149 | 174 | }) |
150 | 175 | } |
151 | 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 | + } | |
152 | 196 | } |
153 | 197 | |
154 | 198 | function Option ({ notifications = 0, imageEl, label, location, selected }) { |
155 | 199 | const className = selected ? '-selected' : '' |
@@ -172,7 +216,15 @@ | ||
172 | 216 | ]), |
173 | 217 | h('div.label', { 'ev-click': goToLocation }, label) |
174 | 218 | ]) |
175 | 219 | } |
176 | - }) | |
220 | + } | |
177 | 221 | } |
178 | 222 | |
223 | +function 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 | +} |
app/html/context.mcss | ||
---|---|---|
@@ -12,25 +12,33 @@ | ||
12 | 12 | overflow-x: hidden |
13 | 13 | |
14 | 14 | border-right: 1px gainsboro solid |
15 | 15 | |
16 | - div.Option {} | |
16 | + div.wrapper { | |
17 | + section { | |
18 | + header { | |
19 | + $colorSubtle | |
20 | + padding: .5rem 1rem | |
21 | + } | |
17 | 22 | |
18 | - header { | |
19 | - $colorSubtle | |
20 | - padding: .5rem 1rem | |
21 | - } | |
23 | + div.Option {} | |
22 | 24 | |
23 | - hr { | |
24 | - border: 1px solid gainsboro | |
25 | - border-bottom: none | |
26 | - margin: 0 | |
25 | + hr { | |
26 | + border: 1px solid gainsboro | |
27 | + border-bottom: none | |
28 | + margin: 0 | |
29 | + } | |
30 | + } | |
27 | 31 | } |
28 | 32 | |
29 | 33 | -one {} |
30 | 34 | -two { |
31 | - div.Option { | |
32 | - padding: 0 1rem | |
35 | + div.wrapper { | |
36 | + section { | |
37 | + div.Option { | |
38 | + padding: 0 1rem | |
39 | + } | |
40 | + } | |
33 | 41 | } |
34 | 42 | } |
35 | 43 | } |
36 | 44 | } |
app/html/scroller.js | ||
---|---|---|
@@ -16,12 +16,9 @@ | ||
16 | 16 | function createScroller (opts = {}) { |
17 | 17 | const { |
18 | 18 | stream, |
19 | 19 | filter = () => pull.filter((msg) => true), |
20 | - // render, | |
21 | - // classList = [], | |
22 | - // prepend = [], | |
23 | - // append = [] | |
20 | + | |
24 | 21 | } = opts |
25 | 22 | |
26 | 23 | const streamToTop = pull( |
27 | 24 | next(stream, {old: false, limit: 100}, ['value', 'timestamp']), |
@@ -33,8 +30,19 @@ | ||
33 | 30 | filter() |
34 | 31 | ) |
35 | 32 | |
36 | 33 | return Scroller(Object.assign({}, opts, { streamToTop, streamToBottom })) |
34 | + // valid Scroller opts : see github.com/mixmix/mutant-scroll | |
35 | + // classList = [], | |
36 | + // prepend = [], | |
37 | + // append = [], | |
38 | + // streamToTop, | |
39 | + // streamToBottom, | |
40 | + // render, | |
41 | + // updateTop = updateTopDefault, | |
42 | + // updateBottom = updateBottomDefault, | |
43 | + // store = MutantArray(), | |
44 | + // cb = (err) => { if (err) throw err } | |
37 | 45 | } |
38 | 46 | } |
39 | 47 | |
40 | 48 | function keyscroll (content) { |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 203359 bytes New file size: 203359 bytes |
package.json | ||
---|---|---|
@@ -33,9 +33,9 @@ | ||
33 | 33 | "markdown-summary": "^1.0.3", |
34 | 34 | "micro-css": "^2.0.1", |
35 | 35 | "morphdom": "^2.3.3", |
36 | 36 | "mutant": "^3.21.2", |
37 | - "mutant-scroll": "0.0.1", | |
37 | + "mutant-scroll": "0.0.3", | |
38 | 38 | "obv-debounce": "^1.0.2", |
39 | 39 | "open-external": "^0.1.1", |
40 | 40 | "patch-profile": "^1.0.2", |
41 | 41 | "patch-settings": "^1.0.0", |
Built with git-ssb-web