diff options
| author | Kit La Touche <kit@transneptune.net> | 2025-02-21 22:18:56 -0500 |
|---|---|---|
| committer | Kit La Touche <kit@transneptune.net> | 2025-02-21 22:53:49 -0500 |
| commit | 9d1dbac74866a6175c65a25bbd8a3ccbe8cf87e4 (patch) | |
| tree | f15b3f0695b948e335774aa4d92a5b064a1c0f10 /ui | |
| parent | 743b59b69857da81b214970ec9252bc918ad243d (diff) | |
| parent | 36cadfe00cacc6a6523f9862d3f7a08a9d0ce611 (diff) | |
Merge branch 'main' into prop/preserve-state
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/app.css | 2 | ||||
| -rw-r--r-- | ui/lib/apiServer.js | 77 | ||||
| -rw-r--r-- | ui/lib/components/Channel.svelte | 8 | ||||
| -rw-r--r-- | ui/lib/components/LogOut.svelte | 2 | ||||
| -rw-r--r-- | ui/lib/components/MessageInput.svelte | 3 | ||||
| -rw-r--r-- | ui/lib/store.js | 61 | ||||
| -rw-r--r-- | ui/routes/(app)/+layout.svelte | 36 | ||||
| -rw-r--r-- | ui/routes/(app)/ch/[channel]/+page.svelte | 9 | ||||
| -rw-r--r-- | ui/routes/(login)/invite/[invite]/+page.svelte | 2 | ||||
| -rw-r--r-- | ui/routes/(login)/login/+page.svelte | 2 | ||||
| -rw-r--r-- | ui/routes/(login)/setup/+page.svelte | 2 | ||||
| -rw-r--r-- | ui/styles/app-bar.css | 2 | ||||
| -rw-r--r-- | ui/styles/fonts.css | 19 | ||||
| -rw-r--r-- | ui/styles/forms.css | 4 | ||||
| -rw-r--r-- | ui/styles/messages.css | 4 | ||||
| -rw-r--r-- | ui/styles/reset.css | 7 | ||||
| -rw-r--r-- | ui/styles/sidebar.css | 3 | ||||
| -rw-r--r-- | ui/styles/textarea.css | 2 |
18 files changed, 115 insertions, 130 deletions
@@ -14,7 +14,7 @@ body { background-color: var(--colour-active-channel-bg); color: var(--dark-text); - font-family: 'Roboto'; + font-family: 'Roboto', sans-serif; } hr { diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index e52daff..c65b743 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -1,5 +1,4 @@ import axios from 'axios'; -import { channelsList, logins, messages } from '$lib/store'; export const apiServer = axios.create({ baseURL: '/api/', @@ -55,75 +54,11 @@ export async function acceptInvite(inviteId, username, password) { } export function subscribeToEvents(resumePoint) { - const eventsUrl = new URL('/api/events', window.location); - eventsUrl.searchParams.append('resume_point', resumePoint); - const evtSource = new EventSource(eventsUrl.toString()); - // TODO: this should process all incoming events and store them. - // TODO: eventually we'll need to handle expiring old info, so as not to use - // infinite browser memory. - /* - * Known message types as of now: - * - created: a channel is created. - * - action: ignore. - * - message: a message is created. - * - action: display message in channel. - * - message_deleted: a message is deleted. - * - action: replace message with <...>. - * - deleted: a channel is deleted. - * - action: remove channel from sidebar. - */ - evtSource.onmessage = (evt) => { - const data = JSON.parse(evt.data); - - switch (data.type) { - case 'login': - onLoginEvent(data); - break; - case 'channel': - onChannelEvent(data); - break; - case 'message': - onMessageEvent(data); - break; + const eventsUrl = apiServer.getUri({ + url: '/events', + params: { + resume_point: resumePoint } - }; - - return evtSource; -} - -function onLoginEvent(data) { - switch (data.event) { - case 'created': - logins.update((value) => value.addLogin(data.id, data.name)); - break; - } -} - -function onChannelEvent(data) { - switch (data.event) { - case 'created': - channelsList.update((value) => value.addChannel(data.id, data.name)); - break; - case 'deleted': - channelsList.update((value) => value.deleteChannel(data.id)); - messages.update((value) => value.deleteChannel(data.id)); - break; - } -} - -function onMessageEvent(data) { - switch (data.event) { - case 'sent': - messages.update((value) => - value.addMessage(data.channel, data.id, { - at: data.at, - sender: data.sender, - body: data.body - }) - ); - break; - case 'deleted': - messages.update((value) => value.deleteMessage(data.id)); - break; - } + }); + return new EventSource(eventsUrl); } diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Channel.svelte index c73340f..4f908d2 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Channel.svelte @@ -2,13 +2,13 @@ let { id, name, active, hasUnreads } = $props(); </script> -<a href="/ch/{id}"> - <li class:active> +<li class:active> + <a href="/ch/{id}"> {#if hasUnreads} <span class="badge has-unreads">❦</span> {:else} <span class="badge has-no-unreads">¶</span> {/if} <span>{name}</span> - </li> -</a> + </a> +</li> diff --git a/ui/lib/components/LogOut.svelte b/ui/lib/components/LogOut.svelte index b699cfd..1cb8fb5 100644 --- a/ui/lib/components/LogOut.svelte +++ b/ui/lib/components/LogOut.svelte @@ -8,7 +8,7 @@ const response = await logOut(); if (200 <= response.status && response.status < 300) { currentUser.set(null); - goto('/login'); + await goto('/login'); } } </script> diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 5869654..1eb1d7b 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -24,7 +24,6 @@ </script> <form bind:this={form} onsubmit={onSubmit}> - <textarea onkeydown={onKeyDown} bind:value {disabled} type="search" placeholder="Say something..." - ></textarea> + <textarea onkeydown={onKeyDown} bind:value {disabled} placeholder="Say something..."></textarea> <button type="submit">»</button> </form> diff --git a/ui/lib/store.js b/ui/lib/store.js index 508320f..afced4c 100644 --- a/ui/lib/store.js +++ b/ui/lib/store.js @@ -15,3 +15,64 @@ export const logins = writable(new Logins()); export const channelsMetaList = writable(new ChannelsMeta({ channelsMetaData })); export const channelsList = writable(new Channels({ channelsMetaList })); export const messages = writable(new Messages()); + +export function onEvent(event) { + switch (event.type) { + case 'login': + onLoginEvent(event); + break; + case 'channel': + onChannelEvent(event); + break; + case 'message': + onMessageEvent(event); + break; + } +} + +onEvent.fromJson = (event) => { + const parsed = JSON.parse(event); + return onEvent(parsed); +}; + +onEvent.fromMessage = (message) => { + const data = message.data; + return onEvent.fromJson(data); +}; + +function onLoginEvent(event) { + switch (event.event) { + case 'created': + logins.update((value) => value.addLogin(event.id, event.name)); + break; + } +} + +function onChannelEvent(event) { + switch (event.event) { + case 'created': + channelsList.update((value) => value.addChannel(event.id, event.name)); + break; + case 'deleted': + channelsList.update((value) => value.deleteChannel(event.id)); + messages.update((value) => value.deleteChannel(event.id)); + break; + } +} + +function onMessageEvent(event) { + switch (event.event) { + case 'sent': + messages.update((value) => + value.addMessage(event.channel, event.id, { + at: event.at, + sender: event.sender, + body: event.body + }) + ); + break; + case 'deleted': + messages.update((value) => value.deleteMessage(event.id)); + break; + } +} diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index 9ade399..888a185 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -1,12 +1,12 @@ <script> - import { page } from '$app/stores'; + import { page } from '$app/state'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; - import { onMount, onDestroy, getContext } from 'svelte'; + import { getContext, onDestroy, onMount } from 'svelte'; import TinyGesture from 'tinygesture'; import { boot, subscribeToEvents } from '$lib/apiServer'; - import { currentUser, logins, channelsList, channelsMetaList, messages } from '$lib/store'; + import { channelsList, channelsMetaList, currentUser, logins, messages, onEvent } from '$lib/store'; import ChannelList from '$lib/components/ChannelList.svelte'; import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; @@ -17,20 +17,11 @@ let pageContext = getContext('page'); let { children } = $props(); let loading = $state(true); - let channel = $derived($page.params.channel); + let channel = $derived(page.params.channel); - let rawChannels; - channelsList.subscribe((val) => { - rawChannels = val.channels; - }); - let rawChannelsMeta; - channelsMetaList.subscribe((val) => { - rawChannelsMeta = val.channelsMeta; - }); - let rawMessages; - messages.subscribe((val) => { - rawMessages = val; - }); + let rawChannels = $derived($channelsList.channels); + let rawChannelsMeta = $derived($channelsMetaList.channelsMeta); + let rawMessages = $derived($messages); let enrichedChannels = $derived.by(() => { const channels = rawChannels; @@ -84,14 +75,15 @@ case 200: onBooted(response.data); events = subscribeToEvents(response.data.resume_point); + events.onmessage = onEvent.fromMessage; break; case 401: currentUser.set(null); - goto('/login'); + await goto('/login'); break; case 503: currentUser.set(null); - goto('/setup'); + await goto('/setup'); break; default: // TODO: display error. @@ -111,18 +103,18 @@ } }); - function beforeUnload(evt) { - evt.preventDefault(); + function onbeforeunload(event) { + event.preventDefault(); if (events !== null) { events.close(); } // For some compat reasons? - evt.returnValue = ''; + event.returnValue = ''; return ''; } </script> -<svelte:window on:beforeunload={beforeUnload} /> +<svelte:window {onbeforeunload} /> <svelte:head> <!-- TODO: unread count? --> diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte index 25bc318..54ebda7 100644 --- a/ui/routes/(app)/ch/[channel]/+page.svelte +++ b/ui/routes/(app)/ch/[channel]/+page.svelte @@ -1,11 +1,11 @@ <script> import { DateTime } from 'luxon'; - import { page } from '$app/stores'; + import { page } from '$app/state'; import ActiveChannel from '$lib/components/ActiveChannel.svelte'; import MessageInput from '$lib/components/MessageInput.svelte'; import { channelsMetaList, messages } from '$lib/store'; - let channel = $derived($page.params.channel); + let channel = $derived(page.params.channel); let messageRuns = $derived($messages.inChannel(channel)); let activeChannel; @@ -53,7 +53,8 @@ } let lastReadCallback = null; - function handleScroll() { + + function onscroll() { clearTimeout(lastReadCallback); // Fine if lastReadCallback is null still. lastReadCallback = setTimeout(setLastRead, 2 * 1000); } @@ -61,7 +62,7 @@ <svelte:window onkeydown={handleKeydown} /> -<div class="active-channel" on:scroll={handleScroll} bind:this={activeChannel}> +<div class="active-channel" {onscroll} bind:this={activeChannel}> <ActiveChannel {messageRuns} /> </div> <div class="create-message"> diff --git a/ui/routes/(login)/invite/[invite]/+page.svelte b/ui/routes/(login)/invite/[invite]/+page.svelte index 132cbc1..0c01286 100644 --- a/ui/routes/(login)/invite/[invite]/+page.svelte +++ b/ui/routes/(login)/invite/[invite]/+page.svelte @@ -18,7 +18,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } diff --git a/ui/routes/(login)/login/+page.svelte b/ui/routes/(login)/login/+page.svelte index a1291ea..9157cef 100644 --- a/ui/routes/(login)/login/+page.svelte +++ b/ui/routes/(login)/login/+page.svelte @@ -16,7 +16,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } diff --git a/ui/routes/(login)/setup/+page.svelte b/ui/routes/(login)/setup/+page.svelte index f162ded..c63f198 100644 --- a/ui/routes/(login)/setup/+page.svelte +++ b/ui/routes/(login)/setup/+page.svelte @@ -16,7 +16,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } diff --git a/ui/styles/app-bar.css b/ui/styles/app-bar.css index 17620ba..0d0a311 100644 --- a/ui/styles/app-bar.css +++ b/ui/styles/app-bar.css @@ -28,7 +28,7 @@ .app-bar > a { line-height: var(--app-bar-height); - font-family: 'Archistico'; + font-family: 'Archistico', serif; letter-spacing: 0.25rem; } diff --git a/ui/styles/fonts.css b/ui/styles/fonts.css index 06f69c8..280f8c6 100644 --- a/ui/styles/fonts.css +++ b/ui/styles/fonts.css @@ -6,6 +6,7 @@ font-style: normal; font-display: swap; } + @font-face { font-family: 'Roboto'; src: url('../fonts/Roboto-Bold.ttf') format('truetype'); @@ -13,6 +14,7 @@ font-style: normal; font-display: swap; } + @font-face { font-family: 'Roboto'; src: url('../fonts/Roboto-Italic.ttf') format('truetype'); @@ -20,6 +22,7 @@ font-style: italic; font-display: swap; } + @font-face { font-family: 'Roboto'; src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); @@ -36,6 +39,7 @@ font-style: normal; font-display: swap; } + @font-face { font-family: 'Archistico'; src: url('../fonts/Archistico_Bold.ttf') format('truetype'); @@ -52,6 +56,7 @@ font-style: normal; font-display: swap; } + @font-face { font-family: 'FiraCode'; src: url('../fonts/FiraCode-Bold.otf') format('opentype'); @@ -59,20 +64,6 @@ font-style: normal; font-display: swap; } -@font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Italic.otf') format('opentype'); - font-weight: normal; - font-style: italic; - font-display: swap; -} -@font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-BoldItalic.otf') format('opentype'); - font-weight: bold; - font-style: italic; - font-display: swap; -} /*** Overlock ***/ @font-face { diff --git a/ui/styles/forms.css b/ui/styles/forms.css index 88a6c41..eb98743 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -5,7 +5,7 @@ label { } label input { - font-family: 'Overlock'; + font-family: 'Overlock', cursive; display: block; width: 90%; padding: 0.25rem; @@ -14,7 +14,7 @@ label input { } form.form > button { - font-family: 'Overlock'; + font-family: 'Overlock', cursive; background-color: var(--colour-input-bg); color: var(--colour-input-text); padding: 0.25rem; diff --git a/ui/styles/messages.css b/ui/styles/messages.css index c4ef106..4890f2c 100644 --- a/ui/styles/messages.css +++ b/ui/styles/messages.css @@ -51,6 +51,7 @@ .message:hover { background-color: var(--colour-message-hover-bg); } + .message:hover * { color: var(--colour-message-hover-text); } @@ -112,11 +113,10 @@ .message-body pre { border: 1px solid #312e81; border-radius: 0.25rem; - background-color: var(--colour-message-run-text); padding: 0.25rem; } .message-body code, .message-body pre { - font-family: 'FiraCode'; + font-family: 'FiraCode', monospace; } diff --git a/ui/styles/reset.css b/ui/styles/reset.css index f9fa505..5a17f02 100644 --- a/ui/styles/reset.css +++ b/ui/styles/reset.css @@ -93,6 +93,7 @@ video { /* font: inherit; */ vertical-align: baseline; } + /* HTML5 display-role reset for older browsers */ article, aside, @@ -107,24 +108,28 @@ nav, section { display: block; } + body { line-height: 1; } + ol, ul { list-style: none; } + blockquote, q { quotes: none; } + blockquote:before, blockquote:after, q:before, q:after { - content: ''; content: none; } + table { border-collapse: collapse; border-spacing: 0; diff --git a/ui/styles/sidebar.css b/ui/styles/sidebar.css index 5e5e16a..c6aab6a 100644 --- a/ui/styles/sidebar.css +++ b/ui/styles/sidebar.css @@ -4,6 +4,8 @@ } .list-nav a { + display: block; + padding: 0.5rem; text-decoration: none; } @@ -12,7 +14,6 @@ } .list-nav li { - padding: 0.5rem; border-radius: 0.5rem; border: 1px solid var(--colour-navbar-border); margin: 0.25rem; diff --git a/ui/styles/textarea.css b/ui/styles/textarea.css index d9be0d6..4b8602c 100644 --- a/ui/styles/textarea.css +++ b/ui/styles/textarea.css @@ -18,7 +18,7 @@ flex-grow: 1; background-color: var(--colour-input-bg); color: var(--colour-input-text); - font-family: 'FiraCode'; + font-family: 'FiraCode', monospace; } .create-message button { |
