summaryrefslogtreecommitdiff
path: root/hi-ui/src
diff options
context:
space:
mode:
authorKit La Touche <kit@transneptune.net>2024-09-19 23:26:39 -0400
committerKit La Touche <kit@transneptune.net>2024-09-27 16:10:36 -0400
commit1d8b828d1bbe0e0daa64f6fc2689799c7169afa0 (patch)
treec51149769901f733e6ec159597d185a0cefeea15 /hi-ui/src
parent80af9cfb858dd18bc1cf26ce213aecd52bd8fc7b (diff)
Add basic browser client
Using Svelte. No tests, no linting, yet. This is just starting to get familiar with things. You'll still have to run the dev server and the dev client builder each in their own terminals. Enjoy!
Diffstat (limited to 'hi-ui/src')
-rw-r--r--hi-ui/src/apiServer.js29
-rw-r--r--hi-ui/src/app.css3
-rw-r--r--hi-ui/src/app.html12
-rw-r--r--hi-ui/src/lib/ActiveChannel.svelte16
-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.svelte27
-rw-r--r--hi-ui/src/lib/LogIn.svelte40
-rw-r--r--hi-ui/src/lib/LogOut.svelte19
-rw-r--r--hi-ui/src/lib/MessageInput.svelte12
-rw-r--r--hi-ui/src/lib/index.js1
-rw-r--r--hi-ui/src/routes/+layout.svelte7
-rw-r--r--hi-ui/src/routes/+page.svelte62
-rw-r--r--hi-ui/src/store.js5
14 files changed, 290 insertions, 0 deletions
diff --git a/hi-ui/src/apiServer.js b/hi-ui/src/apiServer.js
new file mode 100644
index 0000000..92f4dcc
--- /dev/null
+++ b/hi-ui/src/apiServer.js
@@ -0,0 +1,29 @@
+import axios from 'axios';
+
+export const apiServer = axios.create({
+ baseURL: '/api/',
+});
+
+export async function boot() {
+ return apiServer.get('/boot');
+}
+
+export async function logIn(username, password) {
+ const data = {
+ name: username,
+ password,
+ };
+ return apiServer.post('/auth/login', data);
+}
+
+export async function logOut() {
+ return apiServer.post('/auth/logout', {});
+}
+
+export async function listChannels() {
+ return apiServer.get('/channels');
+}
+
+export async function createChannel(name) {
+ return apiServer.post('/channels', { name });
+}
diff --git a/hi-ui/src/app.css b/hi-ui/src/app.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/hi-ui/src/app.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/hi-ui/src/app.html b/hi-ui/src/app.html
new file mode 100644
index 0000000..77a5ff5
--- /dev/null
+++ b/hi-ui/src/app.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ %sveltekit.head%
+ </head>
+ <body data-sveltekit-preload-data="hover">
+ <div style="display: contents">%sveltekit.body%</div>
+ </body>
+</html>
diff --git a/hi-ui/src/lib/ActiveChannel.svelte b/hi-ui/src/lib/ActiveChannel.svelte
new file mode 100644
index 0000000..42aa53f
--- /dev/null
+++ b/hi-ui/src/lib/ActiveChannel.svelte
@@ -0,0 +1,16 @@
+<script>
+ import { activeChannel } from '../store';
+
+ let channelName;
+
+ activeChannel.subscribe((value) => {
+ channelName = value ? value.name : 'none';
+ });
+</script>
+
+<div>
+ Active channel: {channelName}
+</div>
+
+<style>
+</style>
diff --git a/hi-ui/src/lib/Channel.svelte b/hi-ui/src/lib/Channel.svelte
new file mode 100644
index 0000000..80c4505
--- /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-teal-100"
+ class:bg-teal-300={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..584fa61
--- /dev/null
+++ b/hi-ui/src/lib/CreateChannelForm.svelte
@@ -0,0 +1,27 @@
+<script>
+ import { createChannel } from '../apiServer';
+
+ import { channelsList } from '../store';
+
+ let name = '';
+ let disabled = false;
+
+ async function handleSubmit(event) {
+ event.preventDefault();
+ 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={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..df734ee
--- /dev/null
+++ b/hi-ui/src/lib/LogIn.svelte
@@ -0,0 +1,40 @@
+<script>
+ import { logIn } from '../apiServer';
+ import { currentUser } from '../store';
+
+ let disabled = false;
+ let username = '';
+ let password = '';
+
+ async function handleLogin(event) {
+ event.preventDefault();
+ 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={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..439a530
--- /dev/null
+++ b/hi-ui/src/lib/LogOut.svelte
@@ -0,0 +1,19 @@
+<script>
+ import { logOut} from '../apiServer';
+ import { currentUser } from '../store';
+
+ async function handleLogout(event) {
+ event.preventDefault();
+ const response = await logOut();
+ if (200 <= response.status && response.status < 300) {
+ currentUser.update(() => null);
+ }
+ }
+</script>
+
+<form on:submit={handleLogout}>
+ <button type="submit">log out</button>
+</form>
+
+<style>
+</style>
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
new file mode 100644
index 0000000..7896da6
--- /dev/null
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -0,0 +1,12 @@
+<script>
+ function handleSubmit(event) {
+ console.log(event);
+ }
+</script>
+
+<form on:submit={handleSubmit}>
+ <button type="submit">➤</button>
+</form>
+
+<style>
+</style>
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.
diff --git a/hi-ui/src/routes/+layout.svelte b/hi-ui/src/routes/+layout.svelte
new file mode 100644
index 0000000..e3c6561
--- /dev/null
+++ b/hi-ui/src/routes/+layout.svelte
@@ -0,0 +1,7 @@
+<script>
+ import "../app.css";
+</script>
+
+<div class="container mx-auto">
+ <slot />
+</div>
diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
new file mode 100644
index 0000000..4ef26db
--- /dev/null
+++ b/hi-ui/src/routes/+page.svelte
@@ -0,0 +1,62 @@
+<script>
+ import { onMount } from 'svelte';
+
+ import { boot } from '../apiServer';
+ import { currentUser } from '../store';
+
+ import ActiveChannel from '../lib/ActiveChannel.svelte';
+ import ChannelList from '../lib/ChannelList.svelte';
+ import CreateChannelForm from '../lib/CreateChannelForm.svelte';
+ import LogIn from '../lib/LogIn.svelte';
+ import LogOut from '../lib/LogOut.svelte';
+ import MessageInput from '../lib/MessageInput.svelte';
+
+ let user;
+ let loading = true;
+
+ currentUser.subscribe((value) => {
+ user = value;
+ });
+
+ onMount(async () => {
+ try {
+ let response = await boot();
+ switch (response.status) {
+ case 200:
+ currentUser.update(() => ({
+ username: response.data.login.name,
+ }));
+ break;
+ case 401:
+ currentUser.update(() => null);
+ break;
+ default:
+ // TODO: display error.
+ break;
+ }
+ } catch (_) {
+ // I don't want exceptions on non-200 series responses, dammit.
+ }
+ loading = false;
+ });
+</script>
+
+<h1>hi</h1>
+
+{#if loading}
+ <h2>Loading&hellip;</h2>
+{:else if user != null}
+ <LogOut /> @{user.username}
+ <div class="flex flex-row">
+ <div class="basis-1/4 border-solid border-2 border-sky-500">
+ <ChannelList />
+ <CreateChannelForm />
+ </div>
+ <div class="basis-3/4 border-solid border-2 border-sky-500">
+ <ActiveChannel />
+ <MessageInput />
+ </div>
+ </div>
+{:else}
+ <LogIn />
+{/if}
diff --git a/hi-ui/src/store.js b/hi-ui/src/store.js
new file mode 100644
index 0000000..06042c2
--- /dev/null
+++ b/hi-ui/src/store.js
@@ -0,0 +1,5 @@
+import { writable } from 'svelte/store';
+
+export const currentUser = writable(null);
+export const activeChannel = writable(null);
+export const channelsList = writable([]);