Improve message sender rendering
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			- Refactor sender class into parts - Sender name colour depends on mxid, like Element - (colours slightly modified for contrast) - Display blank avatar if loading fails - Remove # parts from mxc - Don't replace member state if loaded state is older
This commit is contained in:
		
							parent
							
								
									a4c7f29ec9
								
							
						
					
					
						commit
						ff196a64bb
					
				
					 5 changed files with 130 additions and 40 deletions
				
			
		| 
						 | 
				
			
			@ -42,7 +42,7 @@ class Chat extends ElemJS {
 | 
			
		|||
			}
 | 
			
		||||
			this.addSubscription("beforeChange", timeline, beforeChangeSubscription)
 | 
			
		||||
 | 
			
		||||
			//Make sure after loading scrollback we don't move the scroll position
 | 
			
		||||
			// Make sure after loading scrollback we don't move the scroll position
 | 
			
		||||
			const beforeScrollbackLoadSubscription = () => {
 | 
			
		||||
				const lastScrollHeight = chatMessages.scrollHeight;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
const lsm = require("./lsm.js")
 | 
			
		||||
 | 
			
		||||
function resolveMxc(url, size, method) {
 | 
			
		||||
	const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
 | 
			
		||||
	let [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
 | 
			
		||||
	id = id.replace(/#.*$/, "")
 | 
			
		||||
	if (size && method) {
 | 
			
		||||
		return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
 | 
			
		||||
	} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										120
									
								
								src/js/sender.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/js/sender.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,120 @@
 | 
			
		|||
const {ElemJS, ejs} = require("./basic.js")
 | 
			
		||||
const {store} = require("./store/store.js")
 | 
			
		||||
const {resolveMxc} = require("./functions.js")
 | 
			
		||||
 | 
			
		||||
function nameToColor(str) {
 | 
			
		||||
	// code from element's react sdk
 | 
			
		||||
	const colors = ["#55a7f0", "#da55ff", "#1bc47c", "#ea657e", "#fd8637", "#22cec6", "#8c8de3", "#71bf22"]
 | 
			
		||||
	let hash = 0
 | 
			
		||||
	let i
 | 
			
		||||
	let chr
 | 
			
		||||
	if (str.length === 0) {
 | 
			
		||||
		return hash
 | 
			
		||||
	}
 | 
			
		||||
	for (i = 0; i < str.length; i++) {
 | 
			
		||||
		chr = str.charCodeAt(i)
 | 
			
		||||
		hash = ((hash << 5) - hash) + chr
 | 
			
		||||
		hash |= 0
 | 
			
		||||
	}
 | 
			
		||||
	hash = Math.abs(hash) % 8
 | 
			
		||||
	return colors[hash]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Avatar extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super("div")
 | 
			
		||||
		this.class("c-message-group__avatar")
 | 
			
		||||
 | 
			
		||||
		this.mxc = undefined
 | 
			
		||||
		this.image = null
 | 
			
		||||
 | 
			
		||||
		this.update(null)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update(mxc) {
 | 
			
		||||
		if (mxc === this.mxc) return
 | 
			
		||||
		this.mxc = mxc
 | 
			
		||||
		this.hasImage = !!mxc
 | 
			
		||||
		if (this.hasImage) {
 | 
			
		||||
			const size = 96
 | 
			
		||||
			const url = resolveMxc(mxc, size, "crop")
 | 
			
		||||
			this.image = ejs("img").class("c-message-group__icon").attribute("src", url).attribute("width", size).attribute("height", size)
 | 
			
		||||
			this.image.on("error", this.onError.bind(this))
 | 
			
		||||
		}
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onError() {
 | 
			
		||||
		this.hasImage = false
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		this.clearChildren()
 | 
			
		||||
		if (this.hasImage) {
 | 
			
		||||
			this.child(this.image)
 | 
			
		||||
		} else {
 | 
			
		||||
			this.child(
 | 
			
		||||
				ejs("div").class("c-message-group__icon", "c-message-group__icon--no-icon")
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Must update at least once to render. */
 | 
			
		||||
class Name extends ElemJS {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super("div")
 | 
			
		||||
		this.class("c-message-group__name")
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Keeps track of whether we have the proper display name or not.
 | 
			
		||||
		 * If we do, then we shoudn't override it with the mxid if the name becomes unavailable.
 | 
			
		||||
		 */
 | 
			
		||||
		this.hasName = false
 | 
			
		||||
		this.name = ""
 | 
			
		||||
		this.mxid = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update(event) {
 | 
			
		||||
		this.mxid = event.state_key
 | 
			
		||||
		if (event.content.displayname) {
 | 
			
		||||
			this.hasName = true
 | 
			
		||||
			this.name = event.content.displayname
 | 
			
		||||
		} else if (!this.hasName) {
 | 
			
		||||
			this.name = this.mxid
 | 
			
		||||
		}
 | 
			
		||||
		this.render()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		// set text
 | 
			
		||||
		this.text(this.name)
 | 
			
		||||
		// set color
 | 
			
		||||
		this.style("color", nameToColor(this.mxid))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Sender {
 | 
			
		||||
	constructor(roomID, mxid) {
 | 
			
		||||
		this.sender = store.rooms.get(roomID).value().members.get(mxid)
 | 
			
		||||
		this.name = new Name()
 | 
			
		||||
		this.avatar = new Avatar()
 | 
			
		||||
		this.sender.subscribe("changeSelf", this.update.bind(this))
 | 
			
		||||
		this.update()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update() {
 | 
			
		||||
		if (this.sender.exists()) {
 | 
			
		||||
			// name
 | 
			
		||||
			this.name.update(this.sender.value())
 | 
			
		||||
 | 
			
		||||
			// avatar
 | 
			
		||||
			this.avatar.update(this.sender.value().content.avatar_url)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
	Sender
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,8 +2,8 @@ const {ElemJS, ejs} = require("./basic.js")
 | 
			
		|||
const {Subscribable} = require("./store/subscribable.js")
 | 
			
		||||
const {store} = require("./store/store.js")
 | 
			
		||||
const {Anchor} = require("./anchor.js")
 | 
			
		||||
const {Sender} = require("./sender.js")
 | 
			
		||||
const lsm = require("./lsm.js")
 | 
			
		||||
const {resolveMxc} = require("./functions.js")
 | 
			
		||||
 | 
			
		||||
let debug = false
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -100,41 +100,6 @@ class Event extends ElemJS {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Sender {
 | 
			
		||||
	constructor(roomID, mxid) {
 | 
			
		||||
		this.sender = store.rooms.get(roomID).value().members.get(mxid)
 | 
			
		||||
		this.sender.subscribe("changeSelf", this.update.bind(this))
 | 
			
		||||
		this.name = new ElemJS("div").class("c-message-group__name")
 | 
			
		||||
		this.avatar = new ElemJS("div").class("c-message-group__avatar")
 | 
			
		||||
		this.displayingGoodData = false
 | 
			
		||||
		this.update()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update() {
 | 
			
		||||
		if (this.sender.exists()) {
 | 
			
		||||
			// name
 | 
			
		||||
			if (this.sender.value().content.displayname) {
 | 
			
		||||
				this.name.text(this.sender.value().content.displayname)
 | 
			
		||||
				this.displayingGoodData = true
 | 
			
		||||
			} else if (!this.displayingGoodData) {
 | 
			
		||||
				this.name.text(this.sender.value().state_key)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// avatar
 | 
			
		||||
			this.avatar.clearChildren()
 | 
			
		||||
			if (this.sender.value().content.avatar_url) {
 | 
			
		||||
				this.avatar.child(
 | 
			
		||||
					ejs("img").class("c-message-group__icon").attribute("src", resolveMxc(this.sender.value().content.avatar_url, 96, "crop"))
 | 
			
		||||
				)
 | 
			
		||||
			} else {
 | 
			
		||||
				this.avatar.child(
 | 
			
		||||
					ejs("div").class("c-message-group__icon", "c-message-group__icon--no-icon")
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EventGroup extends ElemJS {
 | 
			
		||||
	constructor(reactive, list) {
 | 
			
		||||
		super("div")
 | 
			
		||||
| 
						 | 
				
			
			@ -301,7 +266,11 @@ class Timeline extends Subscribable {
 | 
			
		|||
			if (eventData.type === "m.room.member") {
 | 
			
		||||
				// update members
 | 
			
		||||
				if (eventData.membership !== "leave") {
 | 
			
		||||
					this.room.members.get(eventData.state_key).set(eventData)
 | 
			
		||||
					const member = this.room.members.get(eventData.state_key)
 | 
			
		||||
					// only use the latest state
 | 
			
		||||
					if (!member.exists() || eventData.origin_server_ts > member.data.origin_server_ts) {
 | 
			
		||||
						member.set(eventData)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
    border-radius: 50%
 | 
			
		||||
 | 
			
		||||
    &--no-icon
 | 
			
		||||
      background-color: #48d
 | 
			
		||||
      background-color: #bbb
 | 
			
		||||
 | 
			
		||||
  &__intro
 | 
			
		||||
    display: flex
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue