summaryrefslogtreecommitdiff
path: root/hi-ui/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'hi-ui/src/lib')
-rw-r--r--hi-ui/src/lib/ActiveChannel.svelte33
-rw-r--r--hi-ui/src/lib/Channel.svelte23
-rw-r--r--hi-ui/src/lib/ChannelList.svelte34
-rw-r--r--hi-ui/src/lib/CreateChannelForm.svelte26
-rw-r--r--hi-ui/src/lib/LogIn.svelte39
-rw-r--r--hi-ui/src/lib/LogOut.svelte28
-rw-r--r--hi-ui/src/lib/Message.svelte29
-rw-r--r--hi-ui/src/lib/MessageInput.svelte31
-rw-r--r--hi-ui/src/lib/index.js1
9 files changed, 244 insertions, 0 deletions
diff --git a/hi-ui/src/lib/ActiveChannel.svelte b/hi-ui/src/lib/ActiveChannel.svelte
new file mode 100644
index 0000000..84f9119
--- /dev/null
+++ b/hi-ui/src/lib/ActiveChannel.svelte
@@ -0,0 +1,33 @@
+<script>
+ import { activeChannel, events } from '../store';
+ import Message from './Message.svelte';
+
+ let container;
+ $: messages = $events.filter(
+ (ev) => (
+ ev.type === 'message'
+ && $activeChannel !== null
+ && ev.channel.id === $activeChannel.id
+ )
+ );
+
+ // TODO: eventually, store scroll height/last unread in channel? scroll there?
+
+ let scroll = (message) => {
+ message.scrollIntoView();
+ }
+</script>
+
+<div class="container" bind:this={container}>
+ {#each messages as message}
+ <div use:scroll>
+ <Message {...message} />
+ </div>
+ {/each}
+</div>
+
+<style>
+ .container {
+ overflow: scroll;
+ }
+</style>
diff --git a/hi-ui/src/lib/Channel.svelte b/hi-ui/src/lib/Channel.svelte
new file mode 100644
index 0000000..7826c46
--- /dev/null
+++ b/hi-ui/src/lib/Channel.svelte
@@ -0,0 +1,23 @@
+<script>
+ import { activeChannel } from '../store';
+
+ export let id;
+ export let name;
+ let active = false;
+
+ activeChannel.subscribe((value) => {
+ active = value ? value.id == id : false;
+ });
+
+ function activate() {
+ activeChannel.update(() => ({ id, name }));
+ }
+</script>
+
+<li
+ class="cursor-pointer hover:bg-slate-300"
+ class:bg-slate-400={active}
+ on:click={activate}
+>
+ #{name}
+</li>
diff --git a/hi-ui/src/lib/ChannelList.svelte b/hi-ui/src/lib/ChannelList.svelte
new file mode 100644
index 0000000..9f88e24
--- /dev/null
+++ b/hi-ui/src/lib/ChannelList.svelte
@@ -0,0 +1,34 @@
+<script>
+ import { onMount } from 'svelte';
+
+ import { listChannels } from '../apiServer';
+ import { channelsList } from '../store';
+ import Channel from './Channel.svelte';
+
+ let channels;
+ let loading = true;
+
+ channelsList.subscribe((value) => {
+ channels = value;
+ });
+
+ onMount(async () => {
+ let channels = await listChannels();
+ channelsList.update(() => channels.data);
+ loading = false;
+ });
+</script>
+
+<ul class="select-none">
+ {#if loading}
+ <li><em>loading channels&hellip;</em></li>
+ {:else}
+ {#each channels as channel}
+ <Channel {...channel} />
+ {/each}
+ {/if}
+</ul>
+
+<style>
+</style>
+
diff --git a/hi-ui/src/lib/CreateChannelForm.svelte b/hi-ui/src/lib/CreateChannelForm.svelte
new file mode 100644
index 0000000..70dc13d
--- /dev/null
+++ b/hi-ui/src/lib/CreateChannelForm.svelte
@@ -0,0 +1,26 @@
+<script>
+ import { createChannel } from '../apiServer';
+
+ import { channelsList } from '../store';
+
+ let name = '';
+ let disabled = false;
+
+ async function handleSubmit(event) {
+ disabled = true;
+ const response = await createChannel(name);
+ if (200 <= response.status && response.status < 300) {
+ channelsList.update((value) => [...value, response.data]);
+ name = '';
+ }
+ disabled = false;
+ }
+</script>
+
+<form on:submit|preventDefault={handleSubmit}>
+ <input type="text" placeholder="channel name" bind:value={name} disabled={disabled} />
+ <button type="submit">create</button>
+</form>
+
+<style>
+</style>
diff --git a/hi-ui/src/lib/LogIn.svelte b/hi-ui/src/lib/LogIn.svelte
new file mode 100644
index 0000000..1ec6772
--- /dev/null
+++ b/hi-ui/src/lib/LogIn.svelte
@@ -0,0 +1,39 @@
+<script>
+ import { logIn } from '../apiServer';
+ import { currentUser } from '../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>
+
+<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" on:submit|preventDefault={handleLogin}>
+ <div class="mb-4">
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
+ username
+ </label>
+ <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" name="username" type="text" placeholder="username" bind:value={username} disabled={disabled}>
+ </div>
+ <div class="mb-6">
+ <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
+ password
+ </label>
+ <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="password" name="password" type="password" placeholder="password" bind:value={password} disabled={disabled}>
+ </div>
+ <div class="flex items-center justify-between">
+ <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit">
+ sign in or up
+ </button>
+ </div>
+</form>
diff --git a/hi-ui/src/lib/LogOut.svelte b/hi-ui/src/lib/LogOut.svelte
new file mode 100644
index 0000000..738be24
--- /dev/null
+++ b/hi-ui/src/lib/LogOut.svelte
@@ -0,0 +1,28 @@
+<script>
+ import { logOut} from '../apiServer';
+ import { currentUser } from '../store';
+
+ let user;
+
+ currentUser.subscribe((value) => {
+ user = value;
+ });
+
+ async function handleLogout(event) {
+ const response = await logOut();
+ if (200 <= response.status && response.status < 300) {
+ currentUser.update(() => null);
+ }
+ }
+</script>
+
+<form on:submit|preventDefault={handleLogout}>
+ @{user.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/hi-ui/src/lib/Message.svelte b/hi-ui/src/lib/Message.svelte
new file mode 100644
index 0000000..88e47cf
--- /dev/null
+++ b/hi-ui/src/lib/Message.svelte
@@ -0,0 +1,29 @@
+<script>
+ import { currentUser } from '../store';
+ import { deleteMessage } from '../apiServer';
+
+ export let at;
+ export let sender;
+ export let message;
+
+ let timestamp = new Date(at).toTimeString();
+</script>
+
+<div class="hover:bg-zinc-300 flex flex-row">
+ <div class="sender basis-20 text-right mr-1">
+ @{sender.name}:
+ </div>
+ <div class="body grow">
+ {message.body}
+ </div>
+ <div class="timestamp basis-6">
+ <!-- TODO: this is too long and looks awful. -->
+ <!-- {timestamp} -->
+ </div>
+</div>
+
+<style>
+ div:hover .controls {
+ display: block;
+ }
+</style>
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
new file mode 100644
index 0000000..9a8475c
--- /dev/null
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -0,0 +1,31 @@
+<script>
+ import { Input, ButtonGroup, Button } from 'flowbite-svelte';
+ import { CaretRightSolid } from 'flowbite-svelte-icons';
+
+ import { tick } from 'svelte';
+ import { postToChannel } from '../apiServer';
+ import { activeChannel } from '../store';
+
+ let self;
+ let input;
+ $: disabled = $activeChannel == null;
+
+ async function handleSubmit(event) {
+ disabled = true;
+ // TODO try/catch:
+ await postToChannel($activeChannel?.id, input);
+ input = '';
+ disabled = false;
+ await tick();
+ self.focus();
+ }
+</script>
+
+<form on:submit|preventDefault={handleSubmit} class="w-full">
+ <ButtonGroup>
+ <Input disabled={disabled} bind:this={self} bind:value={input} />
+ <Button color="primary" type="submit">
+ <CaretRightSolid class="w-5 h-5" />
+ </Button>
+ </ButtonGroup>
+</form>
diff --git a/hi-ui/src/lib/index.js b/hi-ui/src/lib/index.js
new file mode 100644
index 0000000..856f2b6
--- /dev/null
+++ b/hi-ui/src/lib/index.js
@@ -0,0 +1 @@
+// place files you want to import through the `$lib` alias in this folder.