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…
Reference in a new issue