diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-05-02 01:24:10 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-05-02 01:24:10 -0400 |
| commit | 1035eb815f5a4996d8f546aa4b85da29ccea5d73 (patch) | |
| tree | ae0595abec9ea3db8578daa03a3fb5f2f262db94 | |
| parent | 713afd0b4963460cc37b3767c6b542c8c828d227 (diff) | |
Send messages through an outbox, rather than sending them to the API directly from the UI.
This primarily serves to free up the message input immediately, so that the user can start drafting their next message right away. The wait while a message is being sent is actively disruptive when using Pilcrow on a server with noticable latency (hi.grimoire.ca has around 700ms), and this largely alleviates it.
Unsent messages can be lost if the client is closed or deactivated before they make it to the head of the queue.
| -rw-r--r-- | ui/lib/outbox.svelte.js | 47 | ||||
| -rw-r--r-- | ui/routes/(app)/+layout.js | 2 | ||||
| -rw-r--r-- | ui/routes/(app)/ch/[channel]/+page.svelte | 4 |
3 files changed, 51 insertions, 2 deletions
diff --git a/ui/lib/outbox.svelte.js b/ui/lib/outbox.svelte.js new file mode 100644 index 0000000..0681f29 --- /dev/null +++ b/ui/lib/outbox.svelte.js @@ -0,0 +1,47 @@ +import * as api from './apiServer.js'; +import * as md from './markdown.js'; + +class Message { + constructor(channel, body) { + this.channel = channel; + this.body = body; + this.renderedBody = md.render(body); + } +} + +export class Outbox { + pending = $state([]); + + static empty() { + return new Outbox([]); + } + + constructor(pending) { + this.pending = pending; + } + + send(channel, body) { + this.pending.push(new Message(channel, body)); + this.start(); + } + + start() { + if (this.sending) { + return; + } + // This is a promise transform primarily to keep the management of `this.sending` in one place, + // rather than spreading it across multiple methods. + this.sending = this.drain().finally(() => { + this.sending = null; + }); + } + + async drain() { + while (this.pending.length > 0) { + const { channel, body } = this.pending[0]; + + await api.retry(() => api.postToChannel(channel, body)); + this.pending.shift(); + } + } +} diff --git a/ui/routes/(app)/+layout.js b/ui/routes/(app)/+layout.js index 651bc8c..9c0afa8 100644 --- a/ui/routes/(app)/+layout.js +++ b/ui/routes/(app)/+layout.js @@ -1,8 +1,10 @@ import * as session from '$lib/session.svelte.js'; +import { Outbox } from '$lib/outbox.svelte.js'; export async function load() { let s = await session.boot(); return { + outbox: Outbox.empty(), session: s }; } diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte index c8507cc..9506b67 100644 --- a/ui/routes/(app)/ch/[channel]/+page.svelte +++ b/ui/routes/(app)/ch/[channel]/+page.svelte @@ -7,7 +7,7 @@ import * as api from '$lib/apiServer'; const { data } = $props(); - const { session } = data; + const { session, outbox } = data; let activeChannel; const channel = $derived(page.params.channel); @@ -65,7 +65,7 @@ } async function sendMessage(message) { - await api.postToChannel(channel, message); + outbox.send(channel, message); } async function deleteMessage(id) { |
