summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ui.rs46
-rw-r--r--ui/lib/apiServer.js5
-rw-r--r--ui/lib/components/ActiveChannel.svelte7
-rw-r--r--ui/lib/components/Channel.svelte8
-rw-r--r--ui/lib/components/ChannelList.svelte8
-rw-r--r--ui/lib/components/LogIn.svelte4
-rw-r--r--ui/lib/components/LogOut.svelte8
-rw-r--r--ui/lib/components/MessageInput.svelte32
-rw-r--r--ui/lib/store.js3
-rw-r--r--ui/lib/store/channels.js35
-rw-r--r--ui/routes/(app)/+layout.svelte28
-rw-r--r--ui/routes/(app)/ch/[channel]/+page.svelte15
-rw-r--r--ui/routes/(login)/login/+page.svelte5
13 files changed, 94 insertions, 110 deletions
diff --git a/src/ui.rs b/src/ui.rs
index b296325..34eb6f1 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -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">&raquo;</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&hellip;</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 />