summaryrefslogtreecommitdiff
path: root/ui/lib/store
diff options
context:
space:
mode:
Diffstat (limited to 'ui/lib/store')
-rw-r--r--ui/lib/store/messages.js40
-rw-r--r--ui/lib/store/messages.svelte.js74
2 files changed, 74 insertions, 40 deletions
diff --git a/ui/lib/store/messages.js b/ui/lib/store/messages.js
deleted file mode 100644
index 62c567a..0000000
--- a/ui/lib/store/messages.js
+++ /dev/null
@@ -1,40 +0,0 @@
-export class Messages {
- constructor() {
- this.channels = {};
- }
-
- inChannel(channel) {
- return (this.channels[channel] = this.channels[channel] || []);
- }
-
- addMessage(channel, id, at, sender, body) {
- this.updateChannel(channel, (messages) => [...messages, { id, at, sender, body }]);
- return this;
- }
-
- setMessages(messages) {
- this.channels = {};
- for (let { channel, id, at, sender, body } of messages) {
- this.inChannel(channel).push({ id, at, sender, body });
- }
- return this;
- }
-
- deleteMessage(message) {
- for (let channel in this.channels) {
- this.updateChannel(channel, (messages) => messages.filter((msg) => msg.id != message));
- }
- return this;
- }
-
- deleteChannel(id) {
- delete this.channels[id];
- return this;
- }
-
- updateChannel(channel, callback) {
- let messages = callback(this.inChannel(channel));
- messages.sort((a, b) => a.at - b.at);
- this.channels[channel] = messages;
- }
-}
diff --git a/ui/lib/store/messages.svelte.js b/ui/lib/store/messages.svelte.js
new file mode 100644
index 0000000..c0db71b
--- /dev/null
+++ b/ui/lib/store/messages.svelte.js
@@ -0,0 +1,74 @@
+const RUN_COALESCE_MAX_INTERVAL = 10 /* min */ * 60 /* sec */ * 1000; /* ms */
+
+export class Messages {
+ channels = $state({});
+
+ inChannel(channel) {
+ return this.channels[channel];
+ }
+
+ addMessage(channel, id, { at, sender, body }) {
+ let parsedAt = new Date(at);
+ const message = { id, at: parsedAt, body };
+
+ // You might be thinking, can't this be
+ //
+ // let runs = (this.channels[channel] ||= []);
+ //
+ // Let me tell you, I thought that too. Javascript's semantics allow it. It
+ // didn't work - the first message in each channel was getting lost as the
+ // update to `this.channels` wasn't actually happening. I suspect this is
+ // due to the implementation of Svelte's `$state` rune, but I don't know it
+ // for sure.
+ //
+ // In any case, splitting the read and write up like this has the same
+ // semantics, and _works_. (This time, for sure!)
+ let runs = this.channels[channel] || [];
+
+ let currentRun = runs.slice(-1)[0];
+ if (currentRun === undefined) {
+ currentRun = { sender, messages: [message] };
+ runs.push(currentRun);
+ } else {
+ let lastMessage = currentRun.messages.slice(-1)[0];
+ let newRun =
+ currentRun.sender !== sender || parsedAt - lastMessage.at > RUN_COALESCE_MAX_INTERVAL;
+
+ if (newRun) {
+ currentRun = { sender, messages: [message] };
+ runs.push(currentRun);
+ } else {
+ currentRun.messages.push(message);
+ }
+ }
+
+ this.channels[channel] = runs;
+
+ return this;
+ }
+
+ setMessages(messages) {
+ this.channels = {};
+ for (let { channel, id, at, sender, body } of messages) {
+ this.addMessage(channel, id, { at, sender, body });
+ }
+ return this;
+ }
+
+ deleteMessage(messageId) {
+ for (let channel in this.channels) {
+ this.channels[channel] = this.channels[channel]
+ .map(({ sender, messages }) => ({
+ sender,
+ messages: messages.filter(({ id }) => id != messageId)
+ }))
+ .filter(({ messages }) => messages.length > 0);
+ }
+ return this;
+ }
+
+ deleteChannel(id) {
+ delete this.channels[id];
+ return this;
+ }
+}