Merge pull request 'Add scrollback' (#22) from scrollback into princess
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: #22
This commit is contained in:
		
						commit
						6da9f41519
					
				
					 5 changed files with 84 additions and 49 deletions
				
			
		| 
						 | 
				
			
			@ -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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
							
							
						
						
									
										13
									
								
								src/sass/loading.sass
									
										
									
									
									
										Normal 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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,3 +5,4 @@
 | 
			
		|||
@use "./components/chat"
 | 
			
		||||
@use "./components/chat-input"
 | 
			
		||||
@use "./components/anchor"
 | 
			
		||||
@use "./loading"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue