git ssb

2+

mixmix / ticktack



Commit f30be9436ba56489c01de7d4c5dfef6669137201

WIP scroller with pull-scroll

mix irving committed on 11/19/2017, 4:19:52 AM
Parent: 0e154203de9454a9f8f6a7ecc08aa6f661819f6d

Files changed

app/html/context.jschanged
app/html/scroller.jsadded
app/html/scroller.mcssadded
app/index.jschanged
package-lock.jsonchanged
package.jsonchanged
app/html/context.jsView
@@ -7,8 +7,9 @@
77
88 exports.gives = nest('app.html.context')
99
1010 exports.needs = nest({
11+ 'app.html.scroller': 'first',
1112 'about.html.avatar': 'first',
1213 'about.obs.name': 'first',
1314 'feed.pull.private': 'first',
1415 'feed.pull.rollup': 'first',
@@ -26,26 +27,26 @@
2627 const strings = api.translations.sync.strings()
2728 const myKey = api.keys.sync.id()
2829
2930 var nearby = api.sbot.obs.localPeers()
30- var recentPeersContacted = Dict()
31- // TODO - extract as contact.obs.recentPrivate or something
31+ var recentMsgLog = Dict ()
32+ function updateRecentMsgLog (msg) {
33+ const { author, timestamp } = msg.value
3234
33- pull(
34- next(api.feed.pull.private, {reverse: true, limit: 100, live: false}, ['value', 'timestamp']),
35- pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes?
36- pull.filter(msg => msg.value.content.recps),
37- pull.drain(msg => {
38- msg.value.content.recps
39- .map(recp => typeof recp === 'object' ? recp.link : recp)
40- .filter(recp => recp != myKey)
41- .forEach(recp => {
42- if (recentPeersContacted.has(recp)) return
35+ if (!recentMsgLog.has(author)) {
36+ recentMsgLog.put(author, msg)
37+ return
38+ }
4339
44- recentPeersContacted.put(recp, msg)
45- })
46- })
47- )
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+ }
4849
4950 return h('Context -feed', [
5051 LevelOneContext(),
5152 LevelTwoContext()
@@ -58,24 +59,25 @@
5859 return PAGES_UNDER_DISCOVER.includes(location.page)
5960 || get(location, 'value.private') === undefined
6061 }
6162
62- return h('div.level.-one', [
63+ const prepend = [
6364 // Nearby
6465 computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null),
6566 map(nearby, feedId => Option({
6667 notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO
6768 imageEl: api.about.html.avatar(feedId, 'small'),
6869 label: api.about.obs.name(feedId),
6970 selected: location.feed === feedId,
70- location: computed(recentPeersContacted, recent => {
71+ location: computed(recentMsgLog, recent => {
7172 const lastMsg = recent[feedId]
7273 return lastMsg
7374 ? Object.assign(lastMsg, { feed: feedId })
7475 : { page: 'threadNew', feed: feedId }
7576 }),
7677 }), { comparer: (a, b) => a === b }),
7778
79+ // ---------------------
7880 computed(nearby, n => !isEmpty(n) ? h('hr') : null),
7981
8082 // Discover
8183 Option({
@@ -83,25 +85,38 @@
8385 imageEl: h('i.fa.fa-binoculars'),
8486 label: strings.blogIndex.title,
8587 selected: isDiscoverContext(location),
8688 location: { page: 'blogIndex' },
87- }),
89+ })
90+ ]
8891
89- // Recent Messages
90- map(dictToCollection(recentPeersContacted), ({ key, value }) => {
91- const feedId = key()
92- const lastMsg = value()
93- if (nearby.has(feedId)) return
92+ const { scroller } = api.app.html.scroller({
93+ classList: [ 'level', '-one' ],
94+ prepend,
95+ streamBottom: api.feed.pull.private,
96+ filter: pull(
97+ pull.filter(msg => msg.value.content.type === 'post'), // TODO is this the best way to protect against votes?
98+ 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)
103+ ),
104+ renderer: (msg) => {
105+ const { author } = msg.value
106+ if (nearby.has(author)) return
94107
95108 return Option({
96109 notifications: Math.random() > 0.7 ? Math.floor(Math.random()*9+1) : 0, // TODO
97- imageEl: api.about.html.avatar(feedId),
98- label: api.about.obs.name(feedId),
99- selected: location.feed === feedId,
100- location: Object.assign({}, lastMsg, { feed: feedId }) // TODO make obs?
110+ imageEl: api.about.html.avatar(author),
111+ label: api.about.obs.name(author),
112+ selected: location.feed === author,
113+ location: Object.assign({}, msg, { feed: author }) // TODO make obs?
101114 })
102- }, { comparer: (a, b) => a === b })
103- ])
115+ }
116+ })
117+
118+ return scroller
104119 }
105120
106121 function LevelTwoContext () {
107122 const { key, value, feed: targetUser, page } = location
app/html/scroller.jsView
@@ -1,0 +1,110 @@
1+const nest = require('depnest')
2+const { h } = require('mutant')
3+const pull = require('pull-stream')
4+const pullScroll = require('pull-scroll')
5+const next = require('pull-next-step')
6+
7+exports.gives = nest('app.html.scroller')
8+
9+exports.needs = nest({
10+ 'message.html.render': 'first'
11+})
12+
13+exports.create = function (api) {
14+ return nest('app.html.scroller', createScroller)
15+
16+ function createScroller (opts = {}) {
17+ const {
18+ streamTop,
19+ streamBottom,
20+ filter = (msg) => true,
21+ renderer,
22+ classList = [],
23+ content = h('section.content'),
24+ prepend = [],
25+ append = []
26+ } = opts
27+
28+ if (!streamTop && !streamBottom) throw new Error('Scroller requires a at least one stream: streamTop || streamBottom')
29+ if (!renderer) throw new Error('Scroller expects a renderer')
30+
31+ const scroller = h('Scroller', { classList, style: { overflow: 'auto' } }, [
32+ h('div.wrapper', [
33+ h('header', prepend),
34+ content,
35+ h('footer', append)
36+ ])
37+ ])
38+ // scroller.scroll = keyscroll(content) // used for e.g. reset
39+
40+ draw()
41+
42+ return {
43+ scroller,
44+ content,
45+ // reset,
46+ }
47+
48+ function draw () {
49+ reset()
50+
51+ if (streamTop) {
52+ pull(
53+ next(streamTop, {old: false, limit: 100}, ['value', 'timestamp']),
54+ filter,
55+ // filterDownThrough(),
56+ pullScroll(scroller, content, renderer, true, false)
57+ )
58+ }
59+
60+ if (streamBottom) {
61+ pull(
62+ next(streamBottom, {reverse: true, limit: 100, live: false}, ['value', 'timestamp']),
63+ filter,
64+ // filterUpThrough(),
65+ pullScroll(scroller, content, renderer, false, false)
66+ )
67+ }
68+ }
69+
70+ function reset () {
71+ }
72+
73+ }
74+
75+}
76+
77+function keyscroll (content) {
78+ var curMsgEl
79+
80+ if (!content) return () => {}
81+
82+ content.addEventListener('click', onActivateChild, false)
83+ content.addEventListener('focus', onActivateChild, true)
84+
85+ function onActivateChild (ev) {
86+ for (var el = ev.target; el; el = el.parentNode) {
87+ if (el.parentNode === content) {
88+ curMsgEl = el
89+ return
90+ }
91+ }
92+ }
93+
94+ function selectChild (el) {
95+ if (!el) { return }
96+
97+ ;(el.scrollIntoViewIfNeeded || el.scrollIntoView).call(el)
98+ el.focus()
99+ curMsgEl = el
100+ }
101+
102+ return function scroll (d) {
103+ selectChild((!curMsgEl || d === 'first') ? content.firstChild
104+ : d < 0 ? curMsgEl.previousElementSibling || content.firstChild
105+ : d > 0 ? curMsgEl.nextElementSibling || content.lastChild
106+ : curMsgEl)
107+
108+ return curMsgEl
109+ }
110+}
app/html/scroller.mcssView
@@ -1,0 +1,36 @@
1+Scroller {
2+ display: flex
3+ flex-direction: column
4+
5+ overflow: auto
6+ width: 100%
7+ height: 100%
8+ min-height: 0px
9+
10+ /* div.wrapper { */
11+ /* align-self: center */
12+
13+ /* flex: 1 1 */
14+ /* $threadWidth */
15+ /* padding-top: .5rem */
16+
17+ /* section.content { */
18+ /* div { */
19+ /* border-bottom: solid 1px gainsboro */
20+ /* } */
21+ /* } */
22+ /* } */
23+}
24+
25+Scroller -errors {
26+ div.wrapper {
27+ width: initial
28+ max-width: 100%
29+
30+ section.content div {
31+ border: none
32+ }
33+ }
34+}
35+
36+
app/index.jsView
@@ -9,8 +9,9 @@
99 header: require('./html/header'),
1010 thread: require('./html/thread'),
1111 link: require('./html/link'),
1212 blogCard: require('./html/blogCard'),
13+ scroller: require('./html/scroller'),
1314 },
1415 page: {
1516 blogIndex: require('./page/blogIndex'),
1617 blogNew: require('./page/blogNew'),
package-lock.jsonView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 201691 bytes
New file size: 202220 bytes
package.jsonView
@@ -42,8 +42,9 @@
4242 "patchcore": "^1.12.0",
4343 "pull-next": "^1.0.1",
4444 "pull-next-step": "^1.0.0",
4545 "pull-obv": "^1.3.0",
46+ "pull-scroll": "^1.0.9",
4647 "pull-stream": "^3.6.0",
4748 "read-directory": "^2.1.0",
4849 "require-style": "^1.0.1",
4950 "scuttlebot": "^10.4.4",

Built with git-ssb-web