summaryrefslogtreecommitdiff
path: root/hi-ui/src
diff options
context:
space:
mode:
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.svelte29
-rw-r--r--hi-ui/src/lib/MessageInput.svelte20
-rw-r--r--hi-ui/src/lib/index.js1
-rw-r--r--hi-ui/src/routes/+layout.svelte16
-rw-r--r--hi-ui/src/routes/+page.svelte80
-rw-r--r--hi-ui/src/store.js5
14 files changed, 335 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..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..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..64d26c9
--- /dev/null
+++ b/hi-ui/src/lib/LogOut.svelte
@@ -0,0 +1,29 @@
+<script>
+ import { logOut} from '../apiServer';
+ import { currentUser } from '../store';
+
+ let user;
+
+ currentUser.subscribe((value) => {
+ user = value;
+ });
+
+ 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}>
+ @{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/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
new file mode 100644
index 0000000..4bb4aab
--- /dev/null
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -0,0 +1,20 @@
+<script>
+ let input;
+ let disabled = false;
+
+ function handleSubmit(event) {
+ console.log(event);
+ }
+</script>
+
+<form on:submit={handleSubmit}>
+ <input type="text" class="border rounded px-3" bind:value={input} disabled={disabled}>
+ <button type="submit">➤</button>
+</form>
+
+<style>
+ input[type="text"] {
+ /* TODO: percentage won't handle zoom in/out well. */
+ width: 96%;
+ }
+</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..3673e58
--- /dev/null
+++ b/hi-ui/src/routes/+layout.svelte
@@ -0,0 +1,16 @@
+<script>
+ import "../app.css";
+</script>
+
+<div id="app">
+ <slot />
+</div>
+
+<style>
+ #app {
+ margin: 0;
+ padding: 1rem;
+ height: 100vh;
+ width: 100%;
+ }
+</style>
diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
new file mode 100644
index 0000000..6025b6f
--- /dev/null
+++ b/hi-ui/src/routes/+page.svelte
@@ -0,0 +1,80 @@
+<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 />
+ <div id="interface">
+ <div>
+ <ChannelList />
+ </div>
+ <div>
+ <ActiveChannel />
+ </div>
+ <div>
+ <CreateChannelForm />
+ </div>
+ <div>
+ <MessageInput />
+ </div>
+ </div>
+{:else}
+ <LogIn />
+{/if}
+
+<style>
+ #interface {
+ height: 90%;
+ margin: 1rem;
+ display: grid;
+ grid-template-columns: 18rem auto;
+ grid-template-rows: auto 2rem;
+ grid-gap: 0.25rem;
+ }
+ #interface div {
+ border: 1px solid grey;
+ }
+</style>
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([]);