Sync with matrix and populate rooms list

This commit is contained in:
Cadence Ember 2020-10-15 22:06:41 +13:00
parent dd0b14720e
commit ac6320c12c
Signed by: cadence
GPG key ID: BC1C2C61CF521B17
18 changed files with 465 additions and 47 deletions

View file

@ -80,29 +80,36 @@ function runHint(filename, source) {
hint(source, { hint(source, {
esversion: 9, esversion: 9,
undef: true, undef: true,
unused: true, // unused: true,
loopfunc: true, loopfunc: true,
globals: ["require", "console", "URLSearchParams", "L"], globals: ["console", "URLSearchParams"],
strict: "global", browser: true,
browser: true asi: true,
}) })
const result = hint.data() const result = hint.data()
if (result.errors && result.errors.length) { let problems = 0
if (result.errors) {
for (const error of result.errors) { for (const error of result.errors) {
if (error.evidence) { if (error.evidence) {
const text = error.evidence.replace(/\t/g, " ") const text = error.evidence.replace(/\t/g, " ")
if ([
"W014"
].includes(error.code)) continue
let type = error.code.startsWith("W") ? chalk.yellow("warning") : chalk.red("error") let type = error.code.startsWith("W") ? chalk.yellow("warning") : chalk.red("error")
console.log(`hint: ${type} in ${filename}`) console.log(`hint: ${type} in ${filename}`)
console.log(` ${error.line}:${error.character}: ${error.reason} (${error.code})`) console.log(` ${error.line}:${error.character}: ${error.reason} (${error.code})`)
console.log(chalk.gray( console.log(chalk.gray(
" " " "
+ text.slice(0, error.character) + text.slice(0, error.character)
+ chalk.inverse(text.substr(error.character, 1)) + chalk.inverse(text.substr(error.character, 1))
+ text.slice(error.character+1) + text.slice(error.character+1)
)) ))
problems++
} }
} }
console.log(`hint: ${chalk.cyan(result.errors.length+" problems")} in ${filename}`) }
if (problems) {
console.log(`hint: ${chalk.cyan(problems+" problems")} in ${filename}`)
} else { } else {
console.log(`hint: ${chalk.green("ok")} for ${filename}`) console.log(`hint: ${chalk.green("ok")} for ${filename}`)
} }

View file

@ -2,10 +2,11 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="static/main.css?static=79b6afceb8"> <link rel="stylesheet" type="text/css" href="static/main.css?static=b0aba41b0b">
<script type="module" src="static/groups.js?static=2cc7f0daf8"></script> <script type="module" src="static/groups.js?static=2cc7f0daf8"></script>
<script type="module" src="static/chat-input.js?static=a90499fdac"></script> <script type="module" src="static/chat-input.js?static=a90499fdac"></script>
<script type="module" src="static/room-picker.js?static=596e719ff8"></script> <script type="module" src="static/room-picker.js?static=d92adfaaca"></script>
<script type="module" src="static/sync/sync.js?static=232a64285c"></script>
<title>Carbon</title> <title>Carbon</title>
</head> </head>
<body> <body>
@ -32,7 +33,7 @@
<div class="c-message-group__name">Cadence</div> <div class="c-message-group__name">Cadence</div>
<div class="c-message-group__date">at 4:20 pm</div> <div class="c-message-group__date">at 4:20 pm</div>
</div> </div>
<div class="c-message">the second button is for rooms (gonna call them &quot;channels to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)</div> <div class="c-message">the second button is for rooms (gonna call them &quot;channels&quot; to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)</div>
<div class="c-message">for now, please assume that current groups (&quot;groups v1&quot;) will not be recognised by this client at all</div> <div class="c-message">for now, please assume that current groups (&quot;groups v1&quot;) will not be recognised by this client at all</div>
<div class="c-message">so yeah, press the second button, you see all the ungrouped channels</div> <div class="c-message">so yeah, press the second button, you see all the ungrouped channels</div>
</div> </div>

11
build/static/lsm.js Normal file
View file

@ -0,0 +1,11 @@
function get(name) {
return localStorage.getItem(name)
}
function set(name, value) {
return localStorage.setItem(name, value)
}
window.lsm = {get, set}
export {get, set}

View file

@ -29,7 +29,7 @@ body {
background-color: #2f3135; background-color: #2f3135;
padding: 8px; padding: 8px;
width: 240px; width: 240px;
font-size: 20px; font-size: 18px;
font-weight: 500; font-weight: 500;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin; scrollbar-width: thin;
@ -54,11 +54,13 @@ body {
.c-room__icon { .c-room__icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
background-color: #bbb;
margin-right: 8px; margin-right: 8px;
border-radius: 50%; border-radius: 50%;
flex-shrink: 0; flex-shrink: 0;
} }
.c-room__icon--no-icon {
background-color: #bbb;
}
.c-room__name { .c-room__name {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View file

@ -1,5 +1,15 @@
import {q, ElemJS, ejs} from "./basic.js" import {q, ElemJS, ejs} from "./basic.js"
import {store} from "./store/store.js" import {store} from "./store/store.js"
import * as lsm from "./lsm.js"
function resolveMxc(url, size, method) {
const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
if (size && method) {
return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
} else {
return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`
}
}
class ActiveGroupMarker extends ElemJS { class ActiveGroupMarker extends ElemJS {
constructor() { constructor() {
@ -51,19 +61,47 @@ class Group extends ElemJS {
} }
class Room extends ElemJS { class Room extends ElemJS {
constructor(key, data) { constructor(id, data) {
super("div") super("div")
this.id = id
this.data = data this.data = data
this.class("c-room") this.class("c-room")
this.child(
ejs("div").class("c-room__icon"),
ejs("div").class("c-room__name").text(this.data.name)
)
this.on("click", this.onClick.bind(this)) this.on("click", this.onClick.bind(this))
store.activeRoom.subscribe("changeSelf", this.render.bind(this)) store.activeRoom.subscribe("changeSelf", this.render.bind(this))
this.render()
}
getName() {
let name = this.data.state.events.find(e => e.type === "m.room.name")
if (name) {
name = name.content.name
} else {
const users = this.data.summary["m.heroes"]
const usernames = users.map(u => (u.match(/^@([^:]+):/) || [])[1] || u)
name = usernames.join(", ")
}
return name
}
getIcon() {
const avatar = this.data.state.events.find(e => e.type === "m.room.avatar")
if (avatar) {
return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop")
} else {
return null
}
}
isDirect() {
return store.directs.has(this.id)
}
getGroup() {
return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
} }
onClick() { onClick() {
@ -71,6 +109,16 @@ class Room extends ElemJS {
} }
render() { 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()))
// active
const active = store.activeRoom.value() === this const active = store.activeRoom.value() === this
this.element.classList[active ? "add" : "remove"]("c-room--active") this.element.classList[active ? "add" : "remove"]("c-room--active")
} }
@ -84,8 +132,10 @@ class Rooms extends ElemJS {
this.rooms = [] this.rooms = []
store.rooms.subscribe("askAdd", this.askAdd.bind(this)) store.rooms.subscribe("askAdd", this.askAdd.bind(this))
store.rooms.subscribe("changeItem", this.render.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.activeGroup.subscribe("changeSelf", this.render.bind(this))
store.directs.subscribe("changeItem", this.render.bind(this))
this.render() this.render()
} }
@ -95,18 +145,25 @@ class Rooms extends ElemJS {
store.rooms.addEnd(key, room) 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() { render() {
this.clearChildren() this.clearChildren()
let first = null let first = null
// set room list // set room list
store.rooms.forEach((id, room) => { store.rooms.forEach((id, room) => {
if (room.value().data.group === store.activeGroup.value()) { if (room.value().getGroup() === store.activeGroup.value()) {
if (!first) first = room.value() if (!first) first = room.value()
this.child(room.value()) this.child(room.value())
} }
}) })
// if needed, change the active room to be an item in the room list // if needed, change the active room to be an item in the room list
if (!store.activeRoom.exists() || store.activeRoom.value().data.group !== store.activeGroup.value()) { if (!store.activeRoom.exists() || store.activeRoom.value().getGroup() !== store.activeGroup.value()) {
if (first) { if (first) {
store.activeRoom.set(first) store.activeRoom.set(first)
} else { } else {
@ -136,6 +193,7 @@ class Groups extends ElemJS {
} }
const groups = new Groups() const groups = new Groups()
;[ ;[
{ {
id: "directs", id: "directs",
@ -146,7 +204,7 @@ const groups = new Groups()
id: "channels", id: "channels",
name: "Channels", name: "Channels",
icon: "/static/channels.svg" icon: "/static/channels.svg"
}, }/*,
{ {
id: "123", id: "123",
name: "Fediverse Drama Museum" name: "Fediverse Drama Museum"
@ -158,9 +216,10 @@ const groups = new Groups()
{ {
id: "789", id: "789",
name: "Invidious" name: "Invidious"
} }*/
].forEach(data => store.groups.askAdd(data.id, data)) ].forEach(data => store.groups.askAdd(data.id, data))
/*
;[ ;[
{id: "001", name: "riley", group: store.groups.get("directs").value()}, {id: "001", name: "riley", group: store.groups.get("directs").value()},
{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()}, {id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
@ -188,5 +247,6 @@ const groups = new Groups()
{id: "024", name: "osu", group: store.groups.get("456").value()}, {id: "024", name: "osu", group: store.groups.get("456").value()},
{id: "025", name: "covid", group: store.groups.get("456").value()} {id: "025", name: "covid", group: store.groups.get("456").value()}
].forEach(data => store.rooms.askAdd(data.id, data)) ].forEach(data => store.rooms.askAdd(data.id, data))
*/
store.activeGroup.set(store.groups.get("directs").value()) store.activeGroup.set(store.groups.get("directs").value())

View file

@ -7,14 +7,14 @@ class SubscribeMapList extends Subscribable {
this.inner = inner this.inner = inner
Object.assign(this.events, { Object.assign(this.events, {
addItem: [], addItem: [],
removeItem: [], deleteItem: [],
editItem: [], editItem: [],
changeItem: [], changeItem: [],
askAdd: [] askAdd: []
}) })
Object.assign(this.eventDeps, { Object.assign(this.eventDeps, {
addItem: ["changeItem"], addItem: ["changeItem"],
removeItem: ["changeItem"], deleteItem: ["changeItem"],
editItem: ["changeItem"], editItem: ["changeItem"],
changeItem: [], changeItem: [],
askAdd: [] askAdd: []
@ -59,7 +59,7 @@ class SubscribeMapList extends Subscribable {
const exists = this.map.get(key).exists() const exists = this.map.get(key).exists()
s = this.map.get(key).set(value) s = this.map.get(key).set(value)
if (exists) { if (exists) {
this.broadcast("changeItem", key) this.broadcast("editItem", key)
} else { } else {
this.broadcast("addItem", key) this.broadcast("addItem", key)
} }

View file

@ -0,0 +1,50 @@
import {Subscribable} from "./Subscribable.js"
class SubscribeSet extends Subscribable {
constructor() {
super()
Object.assign(this.events, {
addItem: [],
deleteItem: [],
changeItem: [],
askAdd: []
})
Object.assign(this.eventDeps, {
addItem: ["changeItem"],
deleteItem: ["changeItem"],
changeItem: [],
askAdd: []
})
this.set = new Set()
}
has(key) {
return this.set.has(key)
}
forEach(f) {
for (const key of this.set.keys()) {
f(key)
}
}
askAdd(key) {
this.broadcast("askAdd", key)
}
add(key) {
if (!this.set.has(key)) {
this.set.add(key)
this.broadcast("addItem", key)
}
}
delete(key) {
if (this.set.has(key)) {
this.set.delete(key)
this.broadcast("deleteItem", key)
}
}
}
export {SubscribeSet}

View file

@ -1,9 +1,11 @@
import {SubscribeMapList} from "./SubscribeMapList.js" import {SubscribeMapList} from "./SubscribeMapList.js"
import {SubscribeSet} from "./SubscribeSet.js"
import {SubscribeValue} from "./SubscribeValue.js" import {SubscribeValue} from "./SubscribeValue.js"
const store = { const store = {
groups: new SubscribeMapList(SubscribeValue), groups: new SubscribeMapList(SubscribeValue),
rooms: new SubscribeMapList(SubscribeValue), rooms: new SubscribeMapList(SubscribeValue),
directs: new SubscribeSet(),
activeGroup: new SubscribeValue(), activeGroup: new SubscribeValue(),
activeRoom: new SubscribeValue() activeRoom: new SubscribeValue()
} }

62
build/static/sync/sync.js Normal file
View file

@ -0,0 +1,62 @@
import {store} from "../store/store.js"
import * as lsm from "../lsm.js"
let lastBatch = null
function sync() {
const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`)
url.searchParams.append("access_token", lsm.get("access_token"))
const filter = {
room: {
// pulling more from the timeline massively increases download size
timeline: {
limit: 5
},
// members are not currently needed
state: {
lazy_load_members: true
}
},
presence: {
// presence is not implemented, ignore it
types: []
}
}
url.searchParams.append("filter", JSON.stringify(filter))
url.searchParams.append("timeout", 20000)
if (lastBatch) {
url.searchParams.append("since", lastBatch)
}
return fetch(url.toString()).then(res => res.json()).then(root => {
lastBatch = root.next_batch
return root
})
}
function manageSync(root) {
try {
// set up directs
const directs = root.account_data.events.find(e => e.type === "m.direct")
if (directs) {
Object.values(directs.content).forEach(ids => {
ids.forEach(id => store.directs.add(id))
})
}
// set up rooms
Object.entries(root.rooms.join).forEach(([id, room]) => {
if (!store.rooms.has(id)) {
store.rooms.askAdd(id, room)
}
})
} catch (e) {
console.error(root)
throw e
}
}
function syncLoop() {
return sync().then(manageSync).then(syncLoop)
}
syncLoop()

43
spec.js
View file

@ -10,25 +10,60 @@ module.exports = [
target: "/static/whitney-400.woff" target: "/static/whitney-400.woff"
}, },
{ {
type: "file", type: "js",
source: "/js/basic.js", source: "/js/basic.js",
target: "/static/basic.js" target: "/static/basic.js"
}, },
{ {
type: "file", type: "js",
source: "/js/groups.js", source: "/js/groups.js",
target: "/static/groups.js" target: "/static/groups.js"
}, },
{ {
type: "file", type: "js",
source: "/js/chat-input.js", source: "/js/chat-input.js",
target: "/static/chat-input.js" target: "/static/chat-input.js"
}, },
{ {
type: "file", type: "js",
source: "/js/room-picker.js", source: "/js/room-picker.js",
target: "/static/room-picker.js" target: "/static/room-picker.js"
}, },
{
type: "js",
source: "/js/store/store.js",
target: "/static/store/store.js"
},
{
type: "js",
source: "/js/store/Subscribable.js",
target: "/static/store/Subscribable.js"
},
{
type: "js",
source: "/js/store/SubscribeValue.js",
target: "/static/store/SubscribeValue.js"
},
{
type: "js",
source: "/js/store/SubscribeMapList.js",
target: "/static/store/SubscribeMapList.js"
},
{
type: "js",
source: "/js/store/SubscribeSet.js",
target: "/static/store/SubscribeSet.js"
},
{
type: "js",
source: "/js/sync/sync.js",
target: "/static/sync/sync.js"
},
{
type: "js",
source: "/js/lsm.js",
target: "/static/lsm.js"
},
{ {
type: "file", type: "file",
source: "/assets/fonts/whitney-500.woff", source: "/assets/fonts/whitney-500.woff",

View file

@ -37,6 +37,7 @@ html
script(type="module" src=getStatic("/js/groups.js")) script(type="module" src=getStatic("/js/groups.js"))
script(type="module" src=getStatic("/js/chat-input.js")) script(type="module" src=getStatic("/js/chat-input.js"))
script(type="module" src=getStatic("/js/room-picker.js")) script(type="module" src=getStatic("/js/room-picker.js"))
script(type="module" src=getStatic("/js/sync/sync.js"))
title Carbon title Carbon
body body
main.main main.main
@ -50,7 +51,7 @@ html
.c-chat__inner .c-chat__inner
+message-notice("You've reached the start of the conversation.") +message-notice("You've reached the start of the conversation.")
+message("Cadence", [ +message("Cadence", [
`the second button is for rooms (gonna call them "channels to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)`, `the second button is for rooms (gonna call them "channels" to make discord users happy) that are not in a group (which will be most rooms - few people set up groups because they're so annoying, and many communities of people only need a single chatroom)`,
`for now, please assume that current groups ("groups v1") will not be recognised by this client at all`, `for now, please assume that current groups ("groups v1") will not be recognised by this client at all`,
`so yeah, press the second button, you see all the ungrouped channels` `so yeah, press the second button, you see all the ungrouped channels`
]) ])

11
src/js/lsm.js Normal file
View file

@ -0,0 +1,11 @@
function get(name) {
return localStorage.getItem(name)
}
function set(name, value) {
return localStorage.setItem(name, value)
}
window.lsm = {get, set}
export {get, set}

View file

@ -1,5 +1,15 @@
import {q, ElemJS, ejs} from "./basic.js" import {q, ElemJS, ejs} from "./basic.js"
import {store} from "./store/store.js" import {store} from "./store/store.js"
import * as lsm from "./lsm.js"
function resolveMxc(url, size, method) {
const [server, id] = url.match(/^mxc:\/\/([^/]+)\/(.*)/).slice(1)
if (size && method) {
return `${lsm.get("domain")}/_matrix/media/r0/thumbnail/${server}/${id}?width=${size}&height=${size}&method=${method}`
} else {
return `${lsm.get("domain")}/_matrix/media/r0/download/${server}/${id}`
}
}
class ActiveGroupMarker extends ElemJS { class ActiveGroupMarker extends ElemJS {
constructor() { constructor() {
@ -51,19 +61,47 @@ class Group extends ElemJS {
} }
class Room extends ElemJS { class Room extends ElemJS {
constructor(key, data) { constructor(id, data) {
super("div") super("div")
this.id = id
this.data = data this.data = data
this.class("c-room") this.class("c-room")
this.child(
ejs("div").class("c-room__icon"),
ejs("div").class("c-room__name").text(this.data.name)
)
this.on("click", this.onClick.bind(this)) this.on("click", this.onClick.bind(this))
store.activeRoom.subscribe("changeSelf", this.render.bind(this)) store.activeRoom.subscribe("changeSelf", this.render.bind(this))
this.render()
}
getName() {
let name = this.data.state.events.find(e => e.type === "m.room.name")
if (name) {
name = name.content.name
} else {
const users = this.data.summary["m.heroes"]
const usernames = users.map(u => (u.match(/^@([^:]+):/) || [])[1] || u)
name = usernames.join(", ")
}
return name
}
getIcon() {
const avatar = this.data.state.events.find(e => e.type === "m.room.avatar")
if (avatar) {
return resolveMxc(avatar.content.url || avatar.content.avatar_url, 32, "crop")
} else {
return null
}
}
isDirect() {
return store.directs.has(this.id)
}
getGroup() {
return this.isDirect() ? store.groups.get("directs").value() : store.groups.get("channels").value()
} }
onClick() { onClick() {
@ -71,6 +109,16 @@ class Room extends ElemJS {
} }
render() { 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()))
// active
const active = store.activeRoom.value() === this const active = store.activeRoom.value() === this
this.element.classList[active ? "add" : "remove"]("c-room--active") this.element.classList[active ? "add" : "remove"]("c-room--active")
} }
@ -84,8 +132,10 @@ class Rooms extends ElemJS {
this.rooms = [] this.rooms = []
store.rooms.subscribe("askAdd", this.askAdd.bind(this)) store.rooms.subscribe("askAdd", this.askAdd.bind(this))
store.rooms.subscribe("changeItem", this.render.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.activeGroup.subscribe("changeSelf", this.render.bind(this))
store.directs.subscribe("changeItem", this.render.bind(this))
this.render() this.render()
} }
@ -95,18 +145,25 @@ class Rooms extends ElemJS {
store.rooms.addEnd(key, room) 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() { render() {
this.clearChildren() this.clearChildren()
let first = null let first = null
// set room list // set room list
store.rooms.forEach((id, room) => { store.rooms.forEach((id, room) => {
if (room.value().data.group === store.activeGroup.value()) { if (room.value().getGroup() === store.activeGroup.value()) {
if (!first) first = room.value() if (!first) first = room.value()
this.child(room.value()) this.child(room.value())
} }
}) })
// if needed, change the active room to be an item in the room list // if needed, change the active room to be an item in the room list
if (!store.activeRoom.exists() || store.activeRoom.value().data.group !== store.activeGroup.value()) { if (!store.activeRoom.exists() || store.activeRoom.value().getGroup() !== store.activeGroup.value()) {
if (first) { if (first) {
store.activeRoom.set(first) store.activeRoom.set(first)
} else { } else {
@ -136,6 +193,7 @@ class Groups extends ElemJS {
} }
const groups = new Groups() const groups = new Groups()
;[ ;[
{ {
id: "directs", id: "directs",
@ -146,7 +204,7 @@ const groups = new Groups()
id: "channels", id: "channels",
name: "Channels", name: "Channels",
icon: "/static/channels.svg" icon: "/static/channels.svg"
}, }/*,
{ {
id: "123", id: "123",
name: "Fediverse Drama Museum" name: "Fediverse Drama Museum"
@ -158,9 +216,10 @@ const groups = new Groups()
{ {
id: "789", id: "789",
name: "Invidious" name: "Invidious"
} }*/
].forEach(data => store.groups.askAdd(data.id, data)) ].forEach(data => store.groups.askAdd(data.id, data))
/*
;[ ;[
{id: "001", name: "riley", group: store.groups.get("directs").value()}, {id: "001", name: "riley", group: store.groups.get("directs").value()},
{id: "002", name: "BadAtNames", group: store.groups.get("directs").value()}, {id: "002", name: "BadAtNames", group: store.groups.get("directs").value()},
@ -188,5 +247,6 @@ const groups = new Groups()
{id: "024", name: "osu", group: store.groups.get("456").value()}, {id: "024", name: "osu", group: store.groups.get("456").value()},
{id: "025", name: "covid", group: store.groups.get("456").value()} {id: "025", name: "covid", group: store.groups.get("456").value()}
].forEach(data => store.rooms.askAdd(data.id, data)) ].forEach(data => store.rooms.askAdd(data.id, data))
*/
store.activeGroup.set(store.groups.get("directs").value()) store.activeGroup.set(store.groups.get("directs").value())

View file

@ -7,14 +7,14 @@ class SubscribeMapList extends Subscribable {
this.inner = inner this.inner = inner
Object.assign(this.events, { Object.assign(this.events, {
addItem: [], addItem: [],
removeItem: [], deleteItem: [],
editItem: [], editItem: [],
changeItem: [], changeItem: [],
askAdd: [] askAdd: []
}) })
Object.assign(this.eventDeps, { Object.assign(this.eventDeps, {
addItem: ["changeItem"], addItem: ["changeItem"],
removeItem: ["changeItem"], deleteItem: ["changeItem"],
editItem: ["changeItem"], editItem: ["changeItem"],
changeItem: [], changeItem: [],
askAdd: [] askAdd: []
@ -59,7 +59,7 @@ class SubscribeMapList extends Subscribable {
const exists = this.map.get(key).exists() const exists = this.map.get(key).exists()
s = this.map.get(key).set(value) s = this.map.get(key).set(value)
if (exists) { if (exists) {
this.broadcast("changeItem", key) this.broadcast("editItem", key)
} else { } else {
this.broadcast("addItem", key) this.broadcast("addItem", key)
} }

View file

@ -0,0 +1,50 @@
import {Subscribable} from "./Subscribable.js"
class SubscribeSet extends Subscribable {
constructor() {
super()
Object.assign(this.events, {
addItem: [],
deleteItem: [],
changeItem: [],
askAdd: []
})
Object.assign(this.eventDeps, {
addItem: ["changeItem"],
deleteItem: ["changeItem"],
changeItem: [],
askAdd: []
})
this.set = new Set()
}
has(key) {
return this.set.has(key)
}
forEach(f) {
for (const key of this.set.keys()) {
f(key)
}
}
askAdd(key) {
this.broadcast("askAdd", key)
}
add(key) {
if (!this.set.has(key)) {
this.set.add(key)
this.broadcast("addItem", key)
}
}
delete(key) {
if (this.set.has(key)) {
this.set.delete(key)
this.broadcast("deleteItem", key)
}
}
}
export {SubscribeSet}

View file

@ -1,9 +1,11 @@
import {SubscribeMapList} from "./SubscribeMapList.js" import {SubscribeMapList} from "./SubscribeMapList.js"
import {SubscribeSet} from "./SubscribeSet.js"
import {SubscribeValue} from "./SubscribeValue.js" import {SubscribeValue} from "./SubscribeValue.js"
const store = { const store = {
groups: new SubscribeMapList(SubscribeValue), groups: new SubscribeMapList(SubscribeValue),
rooms: new SubscribeMapList(SubscribeValue), rooms: new SubscribeMapList(SubscribeValue),
directs: new SubscribeSet(),
activeGroup: new SubscribeValue(), activeGroup: new SubscribeValue(),
activeRoom: new SubscribeValue() activeRoom: new SubscribeValue()
} }

62
src/js/sync/sync.js Normal file
View file

@ -0,0 +1,62 @@
import {store} from "../store/store.js"
import * as lsm from "../lsm.js"
let lastBatch = null
function sync() {
const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/sync`)
url.searchParams.append("access_token", lsm.get("access_token"))
const filter = {
room: {
// pulling more from the timeline massively increases download size
timeline: {
limit: 5
},
// members are not currently needed
state: {
lazy_load_members: true
}
},
presence: {
// presence is not implemented, ignore it
types: []
}
}
url.searchParams.append("filter", JSON.stringify(filter))
url.searchParams.append("timeout", 20000)
if (lastBatch) {
url.searchParams.append("since", lastBatch)
}
return fetch(url.toString()).then(res => res.json()).then(root => {
lastBatch = root.next_batch
return root
})
}
function manageSync(root) {
try {
// set up directs
const directs = root.account_data.events.find(e => e.type === "m.direct")
if (directs) {
Object.values(directs.content).forEach(ids => {
ids.forEach(id => store.directs.add(id))
})
}
// set up rooms
Object.entries(root.rooms.join).forEach(([id, room]) => {
if (!store.rooms.has(id)) {
store.rooms.askAdd(id, room)
}
})
} catch (e) {
console.error(root)
throw e
}
}
function syncLoop() {
return sync().then(manageSync).then(syncLoop)
}
syncLoop()

View file

@ -8,7 +8,7 @@ $icon-padding: 8px
background-color: c.$darker background-color: c.$darker
padding: $icon-padding padding: $icon-padding
width: $list-width width: $list-width
font-size: 20px font-size: 18px
font-weight: 500 font-weight: 500
overflow-y: auto overflow-y: auto
scrollbar-width: thin scrollbar-width: thin
@ -32,11 +32,13 @@ $icon-padding: 8px
&__icon &__icon
width: $icon-size width: $icon-size
height: $icon-size height: $icon-size
background-color: #bbb
margin-right: $icon-padding margin-right: $icon-padding
border-radius: 50% border-radius: 50%
flex-shrink: 0 flex-shrink: 0
&--no-icon
background-color: #bbb
&__name &__name
white-space: nowrap white-space: nowrap
overflow: hidden overflow: hidden