summaryrefslogtreecommitdiff
path: root/ui/src/lib/components
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-10 21:05:48 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-10 21:05:48 -0400
commit4401dce2b5545ce8117818812d8e3c8919f5f7fd (patch)
treedf04478e6094a2a8cdd14ecd31b77caacff78de6 /ui/src/lib/components
parent999996961e6e8ebcde125ff0022df875d62817b3 (diff)
Remove redundancy in `hi-ui` directory name.
Diffstat (limited to 'ui/src/lib/components')
-rw-r--r--ui/src/lib/components/ActiveChannel.svelte27
-rw-r--r--ui/src/lib/components/Channel.svelte21
-rw-r--r--ui/src/lib/components/ChannelList.svelte18
-rw-r--r--ui/src/lib/components/CreateChannelForm.svelte23
-rw-r--r--ui/src/lib/components/LogIn.svelte35
-rw-r--r--ui/src/lib/components/LogOut.svelte22
-rw-r--r--ui/src/lib/components/Message.svelte33
-rw-r--r--ui/src/lib/components/MessageInput.svelte30
8 files changed, 209 insertions, 0 deletions
diff --git a/ui/src/lib/components/ActiveChannel.svelte b/ui/src/lib/components/ActiveChannel.svelte
new file mode 100644
index 0000000..978e952
--- /dev/null
+++ b/ui/src/lib/components/ActiveChannel.svelte
@@ -0,0 +1,27 @@
+<script>
+ import { activeChannel, messages } from '$lib/store';
+ import Message from './Message.svelte';
+
+ let container;
+ $: messageList = $activeChannel.isSet() ? $messages.inChannel($activeChannel.get()) : [];
+
+ // TODO: eventually, store scroll height/last unread in channel? scroll there?
+
+ let scroll = (message) => {
+ message.scrollIntoView();
+ }
+</script>
+
+<div class="container" bind:this={container}>
+ {#each messageList as message}
+ <div use:scroll>
+ <Message {...message} />
+ </div>
+ {/each}
+</div>
+
+<style>
+ .container {
+ overflow: scroll;
+ }
+</style>
diff --git a/ui/src/lib/components/Channel.svelte b/ui/src/lib/components/Channel.svelte
new file mode 100644
index 0000000..97fea1f
--- /dev/null
+++ b/ui/src/lib/components/Channel.svelte
@@ -0,0 +1,21 @@
+<script>
+ import { activeChannel } from '$lib/store';
+
+ export let id;
+ export let name;
+ let active = false;
+
+ activeChannel.subscribe((value) => {
+ active = value.is(id);
+ });
+</script>
+
+<li
+ class="rounded-full"
+ class:bg-slate-400={active}
+>
+<a href="/ch/{id}">
+ <span class="badge bg-primary-500">#</span>
+ <span class="flex-auto">{name}</span>
+</a>
+</li>
diff --git a/ui/src/lib/components/ChannelList.svelte b/ui/src/lib/components/ChannelList.svelte
new file mode 100644
index 0000000..e0e5f06
--- /dev/null
+++ b/ui/src/lib/components/ChannelList.svelte
@@ -0,0 +1,18 @@
+<script>
+ import { channelsList } from '$lib/store';
+ import Channel from './Channel.svelte';
+
+ let channels;
+
+ channelsList.subscribe((value) => {
+ channels = value.channels;
+ });
+</script>
+
+<nav class="list-nav">
+ <ul>
+ {#each channels as channel}
+ <Channel {...channel} />
+ {/each}
+ </ul>
+</nav>
diff --git a/ui/src/lib/components/CreateChannelForm.svelte b/ui/src/lib/components/CreateChannelForm.svelte
new file mode 100644
index 0000000..ddcf486
--- /dev/null
+++ b/ui/src/lib/components/CreateChannelForm.svelte
@@ -0,0 +1,23 @@
+<script>
+ import { createChannel } from '$lib/apiServer';
+
+ let name = '';
+ let disabled = false;
+
+ async function handleSubmit(event) {
+ disabled = true;
+ const response = await createChannel(name);
+ if (200 <= response.status && response.status < 300) {
+ name = '';
+ }
+ disabled = false;
+ }
+</script>
+
+<form on:submit|preventDefault={handleSubmit} class="form form-row flex-nowrap">
+ <input type="text" placeholder="create channel" bind:value={name} disabled={disabled} class="input flex-auto h-6 w-9/12" />
+ <button type="submit" class="flex-none w-6 h-6">&#x2795;</button>
+</form>
+
+<style>
+</style>
diff --git a/ui/src/lib/components/LogIn.svelte b/ui/src/lib/components/LogIn.svelte
new file mode 100644
index 0000000..2836e6d
--- /dev/null
+++ b/ui/src/lib/components/LogIn.svelte
@@ -0,0 +1,35 @@
+<script>
+ import { logIn } from '$lib/apiServer';
+ import { currentUser } from '$lib/store';
+
+ let disabled = false;
+ let username = '';
+ let password = '';
+
+ async function handleLogin(event) {
+ disabled = true;
+ const response = await logIn(username, password);
+ if (200 <= response.status && response.status < 300) {
+ currentUser.update(() => ({ username }));
+ username = '';
+ password = '';
+ }
+ disabled = false;
+ }
+</script>
+
+<div class="card m-4 p-4">
+ <form on:submit|preventDefault={handleLogin}>
+ <label class="label" for="username">
+ username
+ <input class="input" name="username" type="text" placeholder="username" bind:value={username} disabled={disabled}>
+ </label>
+ <label class="label" for="password">
+ password
+ <input class="input" name="password" type="password" placeholder="password" bind:value={password} disabled={disabled}>
+ </label>
+ <button class="btn variant-filled" type="submit">
+ sign in or up
+ </button>
+ </form>
+</div>
diff --git a/ui/src/lib/components/LogOut.svelte b/ui/src/lib/components/LogOut.svelte
new file mode 100644
index 0000000..01bef1b
--- /dev/null
+++ b/ui/src/lib/components/LogOut.svelte
@@ -0,0 +1,22 @@
+<script>
+ import { logOut} from '$lib/apiServer';
+ import { currentUser } from '$lib/store';
+
+ async function handleLogout(event) {
+ const response = await logOut();
+ if (200 <= response.status && response.status < 300) {
+ currentUser.update(() => null);
+ }
+ }
+</script>
+
+<form on:submit|preventDefault={handleLogout}>
+ @{$currentUser.username}
+ <button
+ class="border-slate-500 border-solid border-2 font-bold p-1 rounded"
+ type="submit"
+ >log out</button>
+</form>
+
+<style>
+</style>
diff --git a/ui/src/lib/components/Message.svelte b/ui/src/lib/components/Message.svelte
new file mode 100644
index 0000000..d040433
--- /dev/null
+++ b/ui/src/lib/components/Message.svelte
@@ -0,0 +1,33 @@
+<script>
+ import SvelteMarkdown from 'svelte-markdown';
+ import { currentUser, logins } from '$lib/store';
+ import { deleteMessage } from '$lib/apiServer';
+
+ export let at; // XXX: Omitted for now.
+ export let sender;
+ export let body;
+
+ let timestamp = new Date(at).toTimeString();
+ let name;
+ $: name = $logins.get(sender);
+</script>
+
+<div class="card card-hover m-4 relative">
+ <span class="chip variant-soft sticky top-o left-0">
+ <!-- TODO: should this show up for only the first of a run? -->
+ @{name}:
+ </span>
+ <span class="timestamp chip variant-soft absolute top-0 right-0">{at}</span>
+ <section class="p-4">
+ <SvelteMarkdown source={body} />
+ </section>
+</div>
+
+<style>
+ .card .timestamp {
+ display: none;
+ }
+ .card:hover .timestamp {
+ display: flex;
+ }
+</style>
diff --git a/ui/src/lib/components/MessageInput.svelte b/ui/src/lib/components/MessageInput.svelte
new file mode 100644
index 0000000..b33574b
--- /dev/null
+++ b/ui/src/lib/components/MessageInput.svelte
@@ -0,0 +1,30 @@
+<script>
+ import { tick } from 'svelte';
+ import { postToChannel } from '$lib/apiServer';
+ import { activeChannel } from '$lib/store';
+
+ let input;
+ let value;
+ let disabled;
+ activeChannel.subscribe((value) => {
+ disabled = !value.isSet();
+ if (input && !disabled) {
+ 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" />
+ <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>