Display unread/notification counters on rooms
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Cadence Ember 2020-11-25 01:27:41 +13:00
parent e90a2c7da8
commit 229e6903fd
Signed by: cadence
GPG Key ID: BC1C2C61CF521B17
7 changed files with 171 additions and 32 deletions

View File

@ -56,12 +56,65 @@ class Group extends ElemJS {
}
}
class RoomNotifier extends ElemJS {
constructor() {
super("div")
this.classes = [
"notifications",
"unreads",
"none"
]
this.class("c-room__number")
this.state = {
notifications: 0,
unreads: 0
}
this.render()
}
/**
* @param {object} state
* @param {number} [state.notifications]
* @param {number} [state.unreads]
*/
update(state) {
Object.assign(this.state, state)
this.render()
}
render() {
const display = {
number: this.state.notifications || this.state.unreads,
kind: this.state.notifications ? "notifications" : "unreads"
}
// set number
if (display.number) {
this.text(display.number)
} else {
this.text("")
display.kind = "none"
}
// set class
this.classes.forEach(c => {
const name = "c-room__number--" + c
if (c === display.kind) {
this.class(name)
} else {
this.removeClass(name)
}
})
}
}
class Room extends ElemJS {
constructor(id, data) {
super("div")
this.id = id
this.data = data
this.number = new RoomNotifier()
this.timeline = new Timeline(this)
this.group = null
this.members = new SubscribeMapList(SubscribeValue)
@ -75,22 +128,21 @@ class Room extends ElemJS {
}
get order() {
if (this.group) {
let chars = 36
let total = 0
const name = this.getName()
for (let i = 0; i < name.length; i++) {
const c = name[i]
let d = 0
if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10
else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10
else if (c >= "0" && c <= "9") d = +c
total += d * chars ** (-i)
}
return total
let string = ""
if (this.number.state.notifications) {
string += "N"
} else if (this.number.state.unreads) {
string += "U"
} else {
return -this.timeline.latest
string += "_"
}
if (this.group) {
string += this.name
} else {
string += (4000000000000 - this.timeline.latest) // good until 2065 :)
}
console.log(string)
return string
}
getMemberName(mxid) {
@ -174,6 +226,7 @@ class Room extends ElemJS {
this.child(ejs("div").class("c-room__icon", "c-room__icon--no-icon"))
}
this.child(ejs("div").class("c-room__name").text(this.getName()))
this.child(this.number)
// active
const active = store.activeRoom.value() === this
this.element.classList[active ? "add" : "remove"]("c-room--active")
@ -193,6 +246,7 @@ class Rooms extends ElemJS {
store.activeGroup.subscribe("changeSelf", this.render.bind(this))
store.directs.subscribe("changeItem", this.render.bind(this))
store.newEvents.subscribe("changeSelf", this.sort.bind(this))
store.notificationsChange.subscribe("changeSelf", this.sort.bind(this))
this.render()
}

View File

@ -9,7 +9,8 @@ const store = {
directs: new SubscribeSet(),
activeGroup: new SubscribeValue(),
activeRoom: new SubscribeValue(),
newEvents: new Subscribable()
newEvents: new Subscribable(),
notificationsChange: new Subscribable()
}
window.store = store

View File

@ -1,40 +1,54 @@
const {Subscribable} = require("./subscribable.js")
const {SubscribeValue} = require("./subscribe_value.js")
class SubscribeMap extends Subscribable {
constructor() {
super()
Object.assign(this.events, {
addItem: [],
editItem: [],
deleteItem: [],
changeItem: [],
removeItem: []
askSet: []
})
this.map = new Map()
Object.assign(this.eventDeps, {
addItem: ["changeItem"],
editItem: ["changeItem"],
deleteItem: ["changeItem"],
changeItem: [],
askSet: []
})
this.backing = new Map()
}
has(key) {
return this.map.has(key) && this.map.get(key).exists()
return this.backing.has(key)
}
get(key) {
if (this.map.has(key)) {
return this.map.get(key)
} else {
this.map.set(key, new SubscribeValue())
forEach(f) {
for (const key of this.backing.keys()) {
f(key, this.backing.get(key))
}
}
askSet(key, value) {
this.broadcast("askSet", key, value)
}
set(key, value) {
let s
if (this.map.has(key)) {
s = this.map.get(key).set(value)
this.broadcast("changeItem", key)
} else {
s = new SubscribeValue().set(value)
this.map.set(key, s)
const existed = this.backing.has(key)
this.backing.set(key, value)
if (existed) {
this.broadcast("addItem", key)
} else {
this.broadcast("editItem", key)
}
}
delete(key) {
if (this.backing.has(key)) {
this.backing.delete(key)
this.broadcast("deleteItem", key)
}
return s
}
}

View File

@ -54,6 +54,15 @@ class SubscribeMapList extends Subscribable {
}
sort() {
const key = this.list[0]
if (typeof this.map.get(key).value().order === "number") {
this.sortByNumber()
} else {
this.sortByString()
}
}
sortByNumber() {
this.list.sort((a, b) => {
const orderA = this.map.get(a).value().order
const orderB = this.map.get(b).value().order
@ -62,6 +71,20 @@ class SubscribeMapList extends Subscribable {
this.broadcast("changeItem")
}
sortByString() {
this.list.sort((a, b) => {
let r
const orderA = this.map.get(a).value().order
const orderB = this.map.get(b).value().order
if (orderA < orderB) r = -1
else if (orderA > orderB) r = 1
else r = 0
console.log("comparing", orderA, orderB, r)
return r
})
this.broadcast("changeItem")
}
_add(key, value, start) {
let s
if (this.map.has(key)) {

View File

@ -37,6 +37,7 @@ function sync() {
function manageSync(root) {
try {
let newEvents = false
let notificationsChange = false
// set up directs
if (root.account_data) {
@ -66,6 +67,14 @@ function manageSync(root) {
}
}
if (data.ephemeral) timeline.updateEphemeral(data.ephemeral.events)
if (data.unread_notifications) {
timeline.updateNotificationCount(data.unread_notifications.notification_count)
notificationsChange = true
}
if (data["org.matrix.msc2654.unread_count"] != undefined) {
timeline.updateUnreadCount(data["org.matrix.msc2654.unread_count"])
notificationsChange = true
}
})
}
}
@ -109,6 +118,7 @@ function manageSync(root) {
}
if (newEvents) store.newEvents.broadcast("changeSelf")
if (notificationsChange) store.notificationsChange.broadcast("changeSelf")
} catch (e) {
console.error(root)
throw e

View File

@ -1,6 +1,7 @@
const {ElemJS, ejs} = require("./basic.js")
const {Subscribable} = require("./store/subscribable.js")
const {SubscribeValue} = require("./store/subscribe_value.js")
const {SubscribeMap} = require("./store/subscribe_map.js")
const {store} = require("./store/store.js")
const {Anchor} = require("./anchor.js")
const {Sender} = require("./sender.js")
@ -197,6 +198,7 @@ class Timeline extends Subscribable {
this.pending = new Set()
this.pendingEdits = []
this.typing = new SubscribeValue().set([])
this.userReads = new SubscribeMap()
this.from = null
}
@ -280,9 +282,25 @@ class Timeline extends Subscribable {
if (eventData.type === "m.typing") {
this.typing.set(eventData.content.user_ids)
}
if (eventData.type === "m.receipt") {
for (const eventID of Object.keys(eventData.content)) {
for (const user of Object.keys(eventData.content[eventID]["m.read"])) {
this.userReads.set(user, eventID)
}
}
// console.log("Updated read receipts:", this.userReads)
}
}
}
updateUnreadCount(count) {
this.room.number.update({unreads: count})
}
updateNotificationCount(count) {
this.room.number.update({notifications: count})
}
removeEvent(id) {
if (!this.map.has(id)) throw new Error(`Tried to delete event ID ${id} which does not exist`)
this.map.get(id).removeEvent()

View File

@ -43,3 +43,22 @@ $icon-padding: 8px
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
flex: 1
&__number
flex-shrink: 0
line-height: 1
padding: 4px 5px
border-radius: 5px
font-size: 14px
&--none
display: none
&--unreads
background-color: #ddd
color: #111
&--notifications
background-color: #ffac4b
color: #000