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)
|
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 beforeScrollbackLoadSubscription = () => {
|
||||||
const lastScrollHeight = chatMessages.scrollHeight;
|
const lastScrollHeight = chatMessages.scrollHeight;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
const lsm = require("./lsm.js")
|
const lsm = require("./lsm.js")
|
||||||
|
|
||||||
function resolveMxc(url, size, method) {
|
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) {
|
if (size && method) {
|
||||||
return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
|
return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
|
||||||
} else {
|
} 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 {Subscribable} = require("./store/subscribable.js")
|
||||||
const {store} = require("./store/store.js")
|
const {store} = require("./store/store.js")
|
||||||
const {Anchor} = require("./anchor.js")
|
const {Anchor} = require("./anchor.js")
|
||||||
|
const {Sender} = require("./sender.js")
|
||||||
const lsm = require("./lsm.js")
|
const lsm = require("./lsm.js")
|
||||||
const {resolveMxc} = require("./functions.js")
|
|
||||||
|
|
||||||
let debug = false
|
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 {
|
class EventGroup extends ElemJS {
|
||||||
constructor(reactive, list) {
|
constructor(reactive, list) {
|
||||||
super("div")
|
super("div")
|
||||||
|
@ -301,7 +266,11 @@ class Timeline extends Subscribable {
|
||||||
if (eventData.type === "m.room.member") {
|
if (eventData.type === "m.room.member") {
|
||||||
// update members
|
// update members
|
||||||
if (eventData.membership !== "leave") {
|
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%
|
border-radius: 50%
|
||||||
|
|
||||||
&--no-icon
|
&--no-icon
|
||||||
background-color: #48d
|
background-color: #bbb
|
||||||
|
|
||||||
&__intro
|
&__intro
|
||||||
display: flex
|
display: flex
|
||||||
|
|
Loading…
Reference in a new issue