Carbon/src/js/room-picker.js

367 lines
8.4 KiB
JavaScript

const {q, ElemJS, ejs} = require("./basic.js")
const {store} = require("./store/store.js")
const {SubscribeMapList} = require("./store/subscribe_map_list.js")
const {SubscribeValue} = require("./store/subscribe_value.js")
const {Timeline} = require("./timeline.js")
const lsm = require("./lsm.js")
const {resolveMxc, extractLocalpart, extractDisplayName} = require("./functions.js")
class ActiveGroupMarker extends ElemJS {
constructor() {
super(q("#c-group-marker"))
store.activeGroup.subscribe("changeSelf", this.render.bind(this))
}
render() {
if (store.activeGroup.exists()) {
const group = store.activeGroup.value()
this.style("opacity", 1)
this.style("transform", `translateY(${group.element.offsetTop}px)`)
} else {
this.style("opacity", 0)
}
}
}
const activeGroupMarker = new ActiveGroupMarker()
class GroupNotifier extends ElemJS {
constructor() {
super("div")
this.class("c-group__number")
this.state = {}
this.render()
}
update(state) {
Object.assign(this.state, state)
this.render()
}
clear() {
this.state = {}
this.render()
}
render() {
let total = Object.values(this.state).reduce((a, c) => a + c, 0)
if (total > 0) {
this.text(total)
this.class("c-group__number--active")
} else {
this.removeClass("c-group__number--active")
}
}
}
class Group extends ElemJS {
constructor(key, data) {
super("div")
this.data = data
this.order = this.data.order
this.number = new GroupNotifier()
this.class("c-group")
this.child(
(this.data.icon
? ejs("img").class("c-group__icon").attribute("src", this.data.icon)
: ejs("div").class("c-group__icon")
),
this.number,
ejs("div").class("c-group__name").text(this.data.name)
)
this.on("click", this.onClick.bind(this))
store.activeGroup.subscribe("changeSelf", this.render.bind(this))
}
render() {
const active = store.activeGroup.value() === this
this.element.classList[active ? "add" : "remove"]("c-group--active")
}
onClick() {
store.activeGroup.set(this)
}
}
class RoomNotifier extends ElemJS {
constructor(room) {
super("div")
this.class("c-room__number")
this.room = room
this.classes = [
"notifications",
"unreads",
"none"
]
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.informGroup()
this.render()
}
informGroup() {
this.room.getGroup().number.update({[this.room.id]: (
this.state.notifications || (this.state.unreads ? 1 : 0)
)})
}
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)
this.timeline = new Timeline(this)
this.group = null
this.members = new SubscribeMapList(SubscribeValue)
this.class("c-room")
this.on("click", this.onClick.bind(this))
store.activeRoom.subscribe("changeSelf", this.render.bind(this))
this.render()
}
get order() {
let string = ""
if (this.number.state.notifications) {
string += "N"
} else if (this.number.state.unreads) {
string += "U"
} else {
string += "_"
}
if (this.group) {
string += this.name
} else {
string += (4000000000000 - this.timeline.latest) // good until 2065 :)
}
return string
}
getMemberName(mxid) {
if (this.members.has(mxid)) {
const state = this.members.get(mxid).value()
return extractDisplayName(state)
} else {
return extractLocalpart(mxid)
}
}
getHeroes() {
if (this.data.summary) {
return this.data.summary["m.heroes"]
} else {
const me = lsm.get("mx_user_id")
return this.data.state.events.filter(e => e.type === "m.room.member" && e.content.membership === "join" && e.state_key !== me).map(e => e.state_key)
}
}
getName() {
// if the room has a name
let name = this.data.state.events.find(e => e.type === "m.room.name")
if (name && name.content.name) {
return name.content.name
}
// if the room has no name, use its canonical alias
let canonicalAlias = this.data.state.events.find(e => e.type === "m.room.canonical_alias")
if (canonicalAlias && canonicalAlias.content.alias) {
return canonicalAlias.content.alias
}
// if the room has no alias, use the names of its members ("heroes")
const users = this.getHeroes()
if (users && users.length) {
const usernames = users.map(mxid => this.getMemberName(mxid))
return usernames.join(", ")
}
// the room is empty
return "Empty room"
}
getIcon() {
// if the room has a normal avatar
const avatar = this.data.state.events.find(e => e.type === "m.room.avatar")
if (avatar) {
const url = avatar.content.url || avatar.content.avatar_url
if (url) {
return resolveMxc(url, 32, "crop")
}
}
// if the room has no avatar set, use a member's avatar
const users = this.getHeroes()
if (users && users[0] && this.members.has(users[0])) {
// console.log(users[0], this.members.get(users[0]))
const userAvatar = this.members.get(users[0]).value().content.avatar_url
if (userAvatar) {
return resolveMxc(userAvatar, 32, "crop")
}
}
return null
}
isDirect() {
return store.directs.has(this.id)
}
setGroup(id) {
this.group = id
}
getGroup() {
if (this.group) {
return store.groups.get(this.group).value()
} else {
return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
}
}
onClick() {
store.activeRoom.set(this)
}
render() {
this.clearChildren()
// data
const icon = this.getIcon()
if (icon) {
this.child(ejs("img").class("c-room__icon").attribute("src", icon))
} else {
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")
}
}
class Rooms extends ElemJS {
constructor() {
super(q("#c-rooms"))
this.roomData = []
this.rooms = []
store.rooms.subscribe("askAdd", this.askAdd.bind(this))
store.rooms.subscribe("addItem", this.addItem.bind(this))
// store.rooms.subscribe("changeItem", this.render.bind(this))
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()
}
sort() {
store.rooms.sort()
this.render()
}
askAdd(event, {key, data}) {
const room = new Room(key, data)
store.rooms.addEnd(key, room)
}
addItem(event, key) {
const room = store.rooms.get(key).value()
if (room.getGroup() === store.activeGroup.value()) {
this.child(room)
}
}
render() {
this.clearChildren()
let first = null
// set room list
store.rooms.forEach((id, room) => {
if (room.value().getGroup() === store.activeGroup.value()) {
if (!first) first = room.value()
this.child(room.value())
}
})
// if needed, change the active room to be an item in the room list
if (!store.activeRoom.exists() || store.activeRoom.value().getGroup() !== store.activeGroup.value()) {
if (first) {
store.activeRoom.set(first)
} else {
store.activeRoom.delete()
}
}
}
}
const rooms = new Rooms()
class Groups extends ElemJS {
constructor() {
super(q("#c-groups-list"))
store.groups.subscribe("askAdd", this.askAdd.bind(this))
store.groups.subscribe("changeItem", this.render.bind(this))
}
askAdd(event, {key, data}) {
const group = new Group(key, data)
store.groups.addEnd(key, group)
store.groups.sort()
}
render() {
this.clearChildren()
store.groups.forEach((key, item) => {
item.value().number.clear()
this.child(item.value())
})
store.rooms.forEach((id, room) => {
room.value().number.informGroup() // update group notification number
})
}
}
const groups = new Groups()