Merge pull request 'Add scrollback' (#22) from scrollback into princess
continuous-integration/drone/push Build is passing Details

Reviewed-on: #22
This commit is contained in:
cadence 2020-10-26 10:36:43 +00:00
commit 6da9f41519
5 changed files with 84 additions and 49 deletions

View File

@ -27,7 +27,7 @@ class Chat extends ElemJS {
// connect to the new room's timeline updater
if (store.activeRoom.exists()) {
const timeline = store.activeRoom.value().timeline
const subscription = () => {
const beforeChangeSubscription = () => {
// scroll anchor does not work if the timeline is scrolled to the top.
// at the start, when there are not enough messages for a full screen, this is the case.
// once there are enough messages that scrolling is necessary, we initiate a scroll down to activate the scroll anchor.
@ -40,12 +40,29 @@ class Chat extends ElemJS {
}
}, 0)
}
const name = "beforeChange"
this.removableSubscriptions.push({name, target: timeline, subscription})
timeline.subscribe(name, subscription)
this.addSubscription("beforeChange", timeline, beforeChangeSubscription)
//Make sure after loading scrollback we don't move the scroll position
const beforeScrollbackLoadSubscription = () => {
const lastScrollHeight = chatMessages.scrollHeight;
const afterScrollbackLoadSub = () => {
const scrollDiff = chatMessages.scrollHeight - lastScrollHeight;
chatMessages.scrollTop += scrollDiff;
timeline.unsubscribe("afterScrollbackLoad", afterScrollbackLoadSub)
}
timeline.subscribe("afterScrollbackLoad", afterScrollbackLoadSub)
}
this.addSubscription("beforeScrollbackLoad", timeline, beforeScrollbackLoadSubscription)
}
this.render()
}
addSubscription(name, target, subscription) {
this.removableSubscriptions.push({name, target, subscription})
target.subscribe(name, subscription)
}
render() {
this.clearChildren()

View File

@ -33,9 +33,9 @@ function eventSearch(list, event, min = 0, max = NO_MAX) {
}
}
// recurse (below)
if (list[mid].data.origin_server_ts > event.data.origin_server_ts) return eventSearch(list, event, min, mid-1)
if (list[mid].data.origin_server_ts > event.data.origin_server_ts) return eventSearch(list, event, min, mid - 1)
// recurse (above)
else return eventSearch(list, event, mid+1, max)
else return eventSearch(list, event, mid + 1, max)
}
class Event extends ElemJS {
@ -176,16 +176,43 @@ class EventGroup extends ElemJS {
}
}
/** Displays a spinner and creates an event to notify timeline to load more messages */
class LoadMore extends ElemJS {
constructor(id) {
super("div")
this.class("c-message-notice")
this.id = id
this.child(
ejs("div").class("c-message-notice__inner").child(
ejs("span").class("loading-icon"),
ejs("span").text("Loading more...")
)
)
const intersection_observer = new IntersectionObserver(e => this.intersectionHandler(e))
intersection_observer.observe(this.element)
}
intersectionHandler(e) {
if (e.some(e => e.isIntersecting)) {
store.rooms.get(this.id).value().timeline.loadScrollback()
}
}
}
class ReactiveTimeline extends ElemJS {
constructor(id, list) {
super("div")
this.class("c-event-groups")
this.id = id
this.list = list
this.loadMore = new LoadMore(this.id)
this.render()
}
addEvent(event) {
this.loadMore.remove()
// if (debug) console.log("running search", this.list, event)
// if (debug) debugger;
const search = eventSearch(this.list, event)
@ -193,7 +220,7 @@ class ReactiveTimeline extends ElemJS {
if (!search.success) {
if (search.i >= 1) {
// add at end
this.tryAddGroups(event, [search.i-1, search.i])
this.tryAddGroups(event, [search.i - 1, search.i])
} else {
// add at start
this.tryAddGroups(event, [0, -1])
@ -201,6 +228,8 @@ class ReactiveTimeline extends ElemJS {
} else {
this.tryAddGroups(event, [search.i])
}
this.loadMore = new LoadMore(this.id)
this.childAt(0, this.loadMore)
}
tryAddGroups(event, indices) {
@ -233,6 +262,7 @@ class ReactiveTimeline extends ElemJS {
render() {
this.clearChildren()
this.child(this.loadMore)
this.list.forEach(group => this.child(group))
this.anchor = new Anchor()
this.child(this.anchor)
@ -244,11 +274,15 @@ class Timeline extends Subscribable {
super()
Object.assign(this.events, {
beforeChange: [],
afterChange: []
afterChange: [],
beforeScrollbackLoad: [],
afterScrollbackLoad: [],
})
Object.assign(this.eventDeps, {
beforeChange: [],
afterChange: []
afterChange: [],
beforeScrollbackLoad: [],
afterScrollbackLoad: [],
})
this.room = room
this.id = this.room.id
@ -349,16 +383,21 @@ class Timeline extends Subscribable {
url.searchParams.set("access_token", lsm.get("access_token"))
url.searchParams.set("from", this.from)
url.searchParams.set("dir", "b")
url.searchParams.set("limit", 10)
url.searchParams.set("limit", "20")
const filter = {
lazy_load_members: true
}
url.searchParams.set("filter", JSON.stringify(filter))
const root = await fetch(url.toString()).then(res => res.json())
this.broadcast("beforeScrollbackLoad")
this.from = root.end
console.log(this.updateEvents, root.chunk)
// console.log(this.updateEvents, root.chunk)
if (root.state) this.updateStateEvents(root.state)
this.updateEvents(root.chunk)
this.broadcast("afterScrollbackLoad")
}
send(body) {
@ -385,32 +424,8 @@ class Timeline extends Subscribable {
headers: {
"Content-Type": "application/json"
}
})/*.then(() => {
const subscription = () => {
this.removeEvent(id)
this.unsubscribe("afterChange", subscription)
}
this.subscribe("afterChange", subscription)
})*/
})
}
/*
getGroupedEvents() {
let currentSender = Symbol("N/A")
let groups = []
let currentGroup = []
for (const event of this.list) {
if (event.sender === currentSender) {
currentGroup.push(event)
} else {
if (currentGroup.length) groups.push(currentGroup)
currentGroup = [event]
currentSender = event.sender
}
}
if (currentGroup.length) groups.push(currentGroup)
return groups
}
*/
}
module.exports = {Timeline}

13
src/sass/loading.sass Normal file
View File

@ -0,0 +1,13 @@
@keyframes spin
0%
transform: rotate(0deg)
100%
transform: rotate(180deg)
.loading-icon
display: inline-block
background-color: #ccc
width: 12px
height: 12px
margin-right: 6px
animation: spin 0.7s infinite

View File

@ -1,6 +1,8 @@
@use "./base"
@use "./loading.sass"
@use "./colors.sass" as c
.main
justify-content: center
align-items: center
@ -41,19 +43,6 @@
.form-error
color: red
@keyframes spin
0%
transform: rotate(0deg)
100%
transform: rotate(180deg)
.loading-icon
display: inline-block
background-color: #ccc
width: 12px
height: 12px
margin-right: 6px
animation: spin 0.7s infinite
input, button
font-family: inherit

View File

@ -5,3 +5,4 @@
@use "./components/chat"
@use "./components/chat-input"
@use "./components/anchor"
@use "./loading"