diff options
| -rw-r--r-- | src/ui.rs | 46 | ||||
| -rw-r--r-- | ui/lib/apiServer.js | 5 | ||||
| -rw-r--r-- | ui/lib/components/ActiveChannel.svelte | 7 | ||||
| -rw-r--r-- | ui/lib/components/Channel.svelte | 8 | ||||
| -rw-r--r-- | ui/lib/components/ChannelList.svelte | 8 | ||||
| -rw-r--r-- | ui/lib/components/LogIn.svelte | 4 | ||||
| -rw-r--r-- | ui/lib/components/LogOut.svelte | 8 | ||||
| -rw-r--r-- | ui/lib/components/MessageInput.svelte | 32 | ||||
| -rw-r--r-- | ui/lib/store.js | 3 | ||||
| -rw-r--r-- | ui/lib/store/channels.js | 35 | ||||
| -rw-r--r-- | ui/routes/(app)/+layout.svelte | 28 | ||||
| -rw-r--r-- | ui/routes/(app)/ch/[channel]/+page.svelte | 15 | ||||
| -rw-r--r-- | ui/routes/(login)/login/+page.svelte | 5 |
13 files changed, 94 insertions, 110 deletions
@@ -14,25 +14,45 @@ use crate::{app::App, channel, error::Internal, login::Login}; #[folder = "target/ui"] struct Assets; +impl Assets { + fn load(path: impl AsRef<str>) -> Result<Asset, NotFound<String>> { + let path = path.as_ref(); + let mime = mime_guess::from_path(path).first_or_octet_stream(); + + Self::get(path) + .map(|file| Asset(mime, file)) + .ok_or(NotFound(format!("not found: {path}"))) + } + + fn index() -> Result<Asset, Internal> { + // "not found" in this case really is an internal error, as it should + // never happen. `index.html` is a known-valid path. + Ok(Self::load("index.html")?) + } +} + pub fn router() -> Router<App> { Router::new() .route("/*path", get(asset)) .route("/", get(root)) + .route("/login", get(login)) .route("/ch/:channel", get(channel)) } async fn asset(Path(path): Path<String>) -> Result<Asset, NotFound<String>> { - let mime = mime_guess::from_path(&path).first_or_octet_stream(); + Assets::load(path) +} - Assets::get(&path) - .map(|file| Asset(mime, file)) - .ok_or(NotFound(format!("not found: {path}"))) +async fn root(login: Option<Login>) -> Result<impl IntoResponse, Internal> { + if login.is_none() { + Ok(Redirect::temporary("/login").into_response()) + } else { + Ok(Assets::index()?.into_response()) + } } -async fn root() -> Result<Asset, Internal> { - // "not found" in this case really is an internal error, as it should - // never happen. `index.html` is a known-valid path. - Ok(asset(Path(String::from("index.html"))).await?) +async fn login() -> Result<impl IntoResponse, Internal> { + Assets::index() } async fn channel( @@ -40,13 +60,13 @@ async fn channel( login: Option<Login>, Path(channel): Path<channel::Id>, ) -> Result<impl IntoResponse, Internal> { - Ok(if login.is_none() { - Redirect::temporary("/").into_response() + if login.is_none() { + Ok(Redirect::temporary("/").into_response()) } else if app.channels().get(&channel).await?.is_none() { - NotFound(root().await?).into_response() + Ok(NotFound(Assets::index()?).into_response()) } else { - root().await?.into_response() - }) + Ok(Assets::index()?.into_response()) + } } struct Asset(Mime, EmbeddedFile); diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index f6d6148..ccd6e66 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { activeChannel, channelsList, logins, messages } from '$lib/store'; +import { channelsList, logins, messages } from '$lib/store'; export const apiServer = axios.create({ baseURL: '/api/', @@ -66,6 +66,8 @@ export function subscribeToEvents(resume_point) { break; } } + + return evtSource; } function onLoginEvent(data) { @@ -82,7 +84,6 @@ function onChannelEvent(data) { channelsList.update((value) => value.addChannel(data.id, data.name)) break; case 'deleted': - activeChannel.update((value) => value.deleteChannel(data.id)); channelsList.update((value) => value.deleteChannel(data.id)); messages.update((value) => value.deleteChannel(data.id)); break; diff --git a/ui/lib/components/ActiveChannel.svelte b/ui/lib/components/ActiveChannel.svelte index 978e952..ece9f55 100644 --- a/ui/lib/components/ActiveChannel.svelte +++ b/ui/lib/components/ActiveChannel.svelte @@ -1,10 +1,11 @@ <script> - import { activeChannel, messages } from '$lib/store'; + import { messages } from '$lib/store'; import Message from './Message.svelte'; - let container; - $: messageList = $activeChannel.isSet() ? $messages.inChannel($activeChannel.get()) : []; + export let channel = null; + $: messageList = channel !== null ? $messages.inChannel(channel) : []; + let container; // TODO: eventually, store scroll height/last unread in channel? scroll there? let scroll = (message) => { diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Channel.svelte index 97fea1f..e62f0f3 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Channel.svelte @@ -1,13 +1,7 @@ <script> - import { activeChannel } from '$lib/store'; - export let id; export let name; - let active = false; - - activeChannel.subscribe((value) => { - active = value.is(id); - }); + export let active = false; </script> <li diff --git a/ui/lib/components/ChannelList.svelte b/ui/lib/components/ChannelList.svelte index e0e5f06..316e404 100644 --- a/ui/lib/components/ChannelList.svelte +++ b/ui/lib/components/ChannelList.svelte @@ -2,17 +2,15 @@ import { channelsList } from '$lib/store'; import Channel from './Channel.svelte'; - let channels; + export let active = null; - channelsList.subscribe((value) => { - channels = value.channels; - }); + $: channels = $channelsList.channels; </script> <nav class="list-nav"> <ul> {#each channels as channel} - <Channel {...channel} /> + <Channel {...channel} active={active === channel.id} /> {/each} </ul> </nav> diff --git a/ui/lib/components/LogIn.svelte b/ui/lib/components/LogIn.svelte index 2836e6d..e1cda8a 100644 --- a/ui/lib/components/LogIn.svelte +++ b/ui/lib/components/LogIn.svelte @@ -1,4 +1,5 @@ <script> + import { goto } from '$app/navigation'; import { logIn } from '$lib/apiServer'; import { currentUser } from '$lib/store'; @@ -6,13 +7,14 @@ let username = ''; let password = ''; - async function handleLogin(event) { + async function handleLogin() { disabled = true; const response = await logIn(username, password); if (200 <= response.status && response.status < 300) { currentUser.update(() => ({ username })); username = ''; password = ''; + goto('/'); } disabled = false; } diff --git a/ui/lib/components/LogOut.svelte b/ui/lib/components/LogOut.svelte index 01bef1b..ba0861a 100644 --- a/ui/lib/components/LogOut.svelte +++ b/ui/lib/components/LogOut.svelte @@ -1,17 +1,21 @@ <script> + import { goto } from '$app/navigation'; import { logOut} from '$lib/apiServer'; import { currentUser } from '$lib/store'; - async function handleLogout(event) { + async function handleLogout() { const response = await logOut(); if (200 <= response.status && response.status < 300) { currentUser.update(() => null); + goto('/login'); } } </script> <form on:submit|preventDefault={handleLogout}> - @{$currentUser.username} + {#if $currentUser} + @{$currentUser.username} + {/if} <button class="border-slate-500 border-solid border-2 font-bold p-1 rounded" type="submit" diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index b33574b..b2746e0 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -1,30 +1,28 @@ <script> import { tick } from 'svelte'; import { postToChannel } from '$lib/apiServer'; - import { activeChannel } from '$lib/store'; + export let channel = null; let input; - let value; - let disabled; - activeChannel.subscribe((value) => { - disabled = !value.isSet(); - if (input && !disabled) { + let value = ''; + let sending = false; + + $: disabled = (channel === null); + + async function handleSubmit() { + if (channel !== null) { + sending = true; + // TODO try/catch: + await postToChannel(channel, value); + sending = false; + value = ''; + await tick(); input.focus(); } - }); - - async function handleSubmit(event) { - disabled = true; - // TODO try/catch: - await postToChannel($activeChannel.get(), value); - value = ''; - disabled = false; - await tick(); - input.focus(); } </script> <form on:submit|preventDefault={handleSubmit} class="flex flex-row flex-nowrap"> - <input bind:this={input} bind:value={value} disabled={disabled} type="search" class="flex-auto h-6 input rounded-r-none" /> + <input bind:this={input} bind:value={value} disabled={sending || disabled} type="search" class="flex-auto h-6 input rounded-r-none" /> <button color="primary variant-filled-secondary" type="submit" class="flex-none w-6 h-6 btn-icon variant-filled rounded-l-none">»</button> </form> diff --git a/ui/lib/store.js b/ui/lib/store.js index b964b4b..ae17ffa 100644 --- a/ui/lib/store.js +++ b/ui/lib/store.js @@ -1,10 +1,9 @@ import { writable } from 'svelte/store'; -import { ActiveChannel, Channels } from '$lib/store/channels'; +import { Channels } from '$lib/store/channels'; import { Messages } from '$lib/store/messages'; import { Logins } from '$lib/store/logins'; export const currentUser = writable(null); -export const activeChannel = writable(new ActiveChannel()); export const logins = writable(new Logins()); export const channelsList = writable(new Channels()); export const messages = writable(new Messages()); diff --git a/ui/lib/store/channels.js b/ui/lib/store/channels.js index bb6c86c..b57ca7e 100644 --- a/ui/lib/store/channels.js +++ b/ui/lib/store/channels.js @@ -34,38 +34,3 @@ export class Channels { }); } } - -export class ActiveChannel { - constructor() { - this.channel = null; - } - - isSet() { - return this.channel !== null; - } - - get() { - return this.channel; - } - - is(id) { - return this.channel === id; - } - - set(id) { - this.channel = id; - return this; - } - - deleteChannel(id) { - if (this.is(id)) { - return this.clear(); - } - return this; - } - - clear() { - this.channel = null; - return this; - } -} diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index f8744c1..0f8e83c 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -1,20 +1,19 @@ <script> - import { onMount } from 'svelte'; + import { page } from '$app/stores'; + import { goto } from '$app/navigation'; + import { onMount, onDestroy } from 'svelte'; import { boot, subscribeToEvents } from '$lib/apiServer'; import { currentUser, logins, channelsList, messages } from '$lib/store'; import ChannelList from '$lib/components/ChannelList.svelte'; import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; - import LogIn from '$lib/components/LogIn.svelte'; import MessageInput from '$lib/components/MessageInput.svelte'; - let user; let loading = true; + let events = null; - currentUser.subscribe((value) => { - user = value; - }); + $: channel = $page?.params?.channel; function onBooted(boot) { currentUser.update(() => ({ @@ -32,10 +31,11 @@ switch (response.status) { case 200: onBooted(response.data); - subscribeToEvents(response.data.resume_point); + events = subscribeToEvents(response.data.resume_point); break; case 401: currentUser.update(() => null); + goto('/login'); break; default: // TODO: display error. @@ -46,14 +46,20 @@ } loading = false; }); + + onDestroy(async () => { + if (events !== null) { + events.close(); + } + }); </script> {#if loading} <h2>Loading…</h2> -{:else if user != null} +{:else} <div id="interface"> <div class="channel-list"> - <ChannelList /> + <ChannelList active={channel} /> </div> <div class="active-channel"> <slot /> @@ -62,11 +68,9 @@ <CreateChannelForm /> </div> <div class="create-message"> - <MessageInput /> + <MessageInput {channel} /> </div> </div> -{:else} - <LogIn /> {/if} <style> diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte index ef439d0..7bd28d9 100644 --- a/ui/routes/(app)/ch/[channel]/+page.svelte +++ b/ui/routes/(app)/ch/[channel]/+page.svelte @@ -1,17 +1,10 @@ <script> - import { afterNavigate } from '$app/navigation'; import { page } from '$app/stores'; - - import { activeChannel } from '$lib/store'; import ActiveChannel from '$lib/components/ActiveChannel.svelte'; - afterNavigate(async () => { - let { channel } = $page.params; - activeChannel.update((value) => { - value.set(channel) - return value; - }); - }); + $: channel = $page?.params?.channel; </script> -<ActiveChannel /> +<div class="active-channel"> + <ActiveChannel {channel} /> +</div> diff --git a/ui/routes/(login)/login/+page.svelte b/ui/routes/(login)/login/+page.svelte new file mode 100644 index 0000000..c333fdd --- /dev/null +++ b/ui/routes/(login)/login/+page.svelte @@ -0,0 +1,5 @@ +<script> + import LogIn from '$lib/components/LogIn.svelte'; +</script> + +<LogIn /> |
