From 1d8b828d1bbe0e0daa64f6fc2689799c7169afa0 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Thu, 19 Sep 2024 23:26:39 -0400
Subject: 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!
---
 hi-ui/src/lib/MessageInput.svelte | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 hi-ui/src/lib/MessageInput.svelte

(limited to 'hi-ui/src/lib/MessageInput.svelte')

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>
-- 
cgit v1.2.3


From c524b333befc8cc97aa49f73b3ed28bc3b82420c Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Fri, 27 Sep 2024 23:14:58 -0400
Subject: Make message input tolerable as mock-up

It's not robust, and it's not yet able to handle multiline or rich
input. We'll get there.
---
 hi-ui/src/lib/MessageInput.svelte | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'hi-ui/src/lib/MessageInput.svelte')

diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
index 7896da6..4bb4aab 100644
--- a/hi-ui/src/lib/MessageInput.svelte
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -1,12 +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>
-- 
cgit v1.2.3


From 01d995c731c296292cd3f1f9a4702eb96a0bf628 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Mon, 30 Sep 2024 23:14:36 -0400
Subject: Absorb and display events

At least message-type ones, and at least without styling or memory-limit
concerns.
---
 hi-ui/src/apiServer.js             | 16 ++++++++++++++++
 hi-ui/src/lib/ActiveChannel.svelte | 20 ++++++++++++++++----
 hi-ui/src/lib/MessageInput.svelte  | 17 +++++++++++++++--
 hi-ui/src/routes/+page.svelte      |  3 ++-
 hi-ui/src/store.js                 |  1 +
 5 files changed, 50 insertions(+), 7 deletions(-)

(limited to 'hi-ui/src/lib/MessageInput.svelte')

diff --git a/hi-ui/src/apiServer.js b/hi-ui/src/apiServer.js
index 92f4dcc..7365a36 100644
--- a/hi-ui/src/apiServer.js
+++ b/hi-ui/src/apiServer.js
@@ -1,4 +1,5 @@
 import axios from 'axios';
+import { events } from './store';
 
 export const apiServer = axios.create({
   baseURL: '/api/',
@@ -27,3 +28,18 @@ export async function listChannels() {
 export async function createChannel(name) {
   return apiServer.post('/channels', { name });
 }
+
+export async function postToChannel(channelId, message) {
+  return apiServer.post(`/channels/${channelId}`, { message });
+}
+
+export function subscribeToEvents() {
+  const evtSource = new EventSource("/api/events");
+  // TODO: this should process all incoming events and store them.
+  // TODO: eventually we'll need to handle expiring old info, so as not to use
+  //       infinite browser memory.
+  evtSource.onmessage = (evt) => {
+    const data = JSON.parse(evt.data);
+    events.update((value) => [...value, data]);
+  }
+}
diff --git a/hi-ui/src/lib/ActiveChannel.svelte b/hi-ui/src/lib/ActiveChannel.svelte
index 42aa53f..680a785 100644
--- a/hi-ui/src/lib/ActiveChannel.svelte
+++ b/hi-ui/src/lib/ActiveChannel.svelte
@@ -1,15 +1,27 @@
 <script>
-    import { activeChannel } from '../store';
+    import { activeChannel, events } from '../store';
 
-    let channelName;
+    let channel;
+    let allMessages = [];
+    $: messages = allMessages.filter(
+        (ev) => ev.type === 'message' && channel !== null && ev.channel.id === channel.id
+    );
 
     activeChannel.subscribe((value) => {
-        channelName = value ? value.name : 'none';
+        channel = value;
+    });
+
+    events.subscribe((value) => {
+        allMessages = value;
     });
 </script>
 
 <div>
-    Active channel: {channelName}
+    {#each messages as message}
+        <div>
+            <pre><tt>{message.at} @{message.sender.name}: {message.message.body}</tt></pre>
+        </div>
+    {/each}
 </div>
 
 <style>
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
index 4bb4aab..96e9577 100644
--- a/hi-ui/src/lib/MessageInput.svelte
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -1,9 +1,22 @@
 <script>
+    import { postToChannel } from '../apiServer';
+    import { activeChannel } from '../store';
+
     let input;
     let disabled = false;
+    let activeChannelId;
+
+    activeChannel.subscribe((value) => {
+        activeChannelId = value ? value.id : null;
+    });
 
-    function handleSubmit(event) {
-        console.log(event);
+    async function handleSubmit(event) {
+        event.preventDefault();
+        disabled = true;
+        // TODO try/catch:
+        await postToChannel(activeChannelId, input);
+        input = '';
+        disabled = false;
     }
 </script>
 
diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
index 6d60b0c..646665e 100644
--- a/hi-ui/src/routes/+page.svelte
+++ b/hi-ui/src/routes/+page.svelte
@@ -1,7 +1,7 @@
 <script>
 	import { onMount } from 'svelte';
 
-	import { boot } from '../apiServer';
+	import { boot, subscribeToEvents } from '../apiServer';
 	import { currentUser } from '../store';
 
     import ActiveChannel from '../lib/ActiveChannel.svelte';
@@ -26,6 +26,7 @@
                     currentUser.update(() => ({
                         username: response.data.login.name,
                     }));
+                    subscribeToEvents();
                     break;
                 case 401:
                     currentUser.update(() => null);
diff --git a/hi-ui/src/store.js b/hi-ui/src/store.js
index 06042c2..a9d9421 100644
--- a/hi-ui/src/store.js
+++ b/hi-ui/src/store.js
@@ -3,3 +3,4 @@ import { writable } from 'svelte/store';
 export const currentUser = writable(null);
 export const activeChannel = writable(null);
 export const channelsList = writable([]);
+export const events = writable([]);
-- 
cgit v1.2.3


From 30c13478d61065a512f5bc8824fecbf2ee6afc81 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Thu, 3 Oct 2024 23:30:10 -0400
Subject: Handle basics of interface scrolling

---
 hi-ui/src/apiServer.js                 | 61 ++++++++++++++++++++++++++++++++--
 hi-ui/src/lib/ActiveChannel.svelte     | 31 +++++++++--------
 hi-ui/src/lib/CreateChannelForm.svelte |  3 +-
 hi-ui/src/lib/LogIn.svelte             |  3 +-
 hi-ui/src/lib/LogOut.svelte            |  3 +-
 hi-ui/src/lib/Message.svelte           | 38 +++++++++++++++++++++
 hi-ui/src/lib/MessageInput.svelte      | 18 +++++-----
 hi-ui/src/routes/+page.svelte          |  5 ++-
 8 files changed, 130 insertions(+), 32 deletions(-)
 create mode 100644 hi-ui/src/lib/Message.svelte

(limited to 'hi-ui/src/lib/MessageInput.svelte')

diff --git a/hi-ui/src/apiServer.js b/hi-ui/src/apiServer.js
index 7365a36..5e521de 100644
--- a/hi-ui/src/apiServer.js
+++ b/hi-ui/src/apiServer.js
@@ -1,5 +1,5 @@
 import axios from 'axios';
-import { events } from './store';
+import { activeChannel, channelsList, events } from './store';
 
 export const apiServer = axios.create({
   baseURL: '/api/',
@@ -33,13 +33,70 @@ export async function postToChannel(channelId, message) {
   return apiServer.post(`/channels/${channelId}`, { message });
 }
 
+export async function deleteMessage(messageId) {
+  // TODO
+}
+
 export function subscribeToEvents() {
   const evtSource = new EventSource("/api/events");
+  events.update(() => []);
   // TODO: this should process all incoming events and store them.
   // TODO: eventually we'll need to handle expiring old info, so as not to use
   //       infinite browser memory.
+  /*
+   * Known message types as of now:
+   * - created: a channel is created.
+   *   - action: ignore.
+   * - message: a message is created.
+   *   - action: display message in channel.
+   * - message_deleted: a message is deleted.
+   *   - action: replace message with <...>.
+   * - deleted: a channel is deleted.
+   *   - action: remove channel from sidebar.
+  */
   evtSource.onmessage = (evt) => {
     const data = JSON.parse(evt.data);
-    events.update((value) => [...value, data]);
+
+    switch (data.type) {
+      case 'created':
+        break;
+      case 'message':
+        events.update((value) => {
+          const eventList = [...value, data];
+          eventList.sort((a, b) => a.at - b.at);
+          return eventList;
+        });
+        break;
+      case 'message_deleted':
+        events.update((value) => {
+          const eventList = value.map((el) => {
+            if (el.message?.id === data.message) {
+              el.message.body = '&laquo;&hellip;&raquo;';
+              return el
+            } else {
+              return el;
+            }
+          });
+          return eventList;
+        });
+        break;
+      case 'deleted':
+        activeChannel.update((value) => {
+          if (value?.id === data.channel) {
+            return null;
+          }
+          return value;
+        });
+        channelsList.update((value) => {
+          const channelIndex = value.map((e) => e.id).indexOf(data.channel);
+          if (channelIndex !== -1) {
+            value.splice(channelIndex, 1);
+          }
+          return value;
+        });
+        break;
+      default:
+        break;
+    }
   }
 }
diff --git a/hi-ui/src/lib/ActiveChannel.svelte b/hi-ui/src/lib/ActiveChannel.svelte
index 680a785..84f9119 100644
--- a/hi-ui/src/lib/ActiveChannel.svelte
+++ b/hi-ui/src/lib/ActiveChannel.svelte
@@ -1,28 +1,33 @@
 <script>
     import { activeChannel, events } from '../store';
+    import Message from './Message.svelte';
 
-    let channel;
-    let allMessages = [];
-    $: messages = allMessages.filter(
-        (ev) => ev.type === 'message' && channel !== null && ev.channel.id === channel.id
+    let container;
+    $: messages = $events.filter(
+        (ev) => (
+            ev.type === 'message'
+            && $activeChannel !== null
+            && ev.channel.id === $activeChannel.id
+        )
     );
 
-    activeChannel.subscribe((value) => {
-        channel = value;
-    });
+    // TODO: eventually, store scroll height/last unread in channel? scroll there?
 
-    events.subscribe((value) => {
-        allMessages = value;
-    });
+    let scroll = (message) => {
+        message.scrollIntoView();
+    }
 </script>
 
-<div>
+<div class="container" bind:this={container}>
     {#each messages as message}
-        <div>
-            <pre><tt>{message.at} @{message.sender.name}: {message.message.body}</tt></pre>
+        <div use:scroll>
+            <Message {...message} />
         </div>
     {/each}
 </div>
 
 <style>
+    .container {
+        overflow: scroll;
+    }
 </style>
diff --git a/hi-ui/src/lib/CreateChannelForm.svelte b/hi-ui/src/lib/CreateChannelForm.svelte
index 584fa61..70dc13d 100644
--- a/hi-ui/src/lib/CreateChannelForm.svelte
+++ b/hi-ui/src/lib/CreateChannelForm.svelte
@@ -7,7 +7,6 @@
     let disabled = false;
 
     async function handleSubmit(event) {
-        event.preventDefault();
         disabled = true;
         const response = await createChannel(name);
         if (200 <= response.status && response.status < 300) {
@@ -18,7 +17,7 @@
     }
 </script>
 
-<form on:submit={handleSubmit}>
+<form on:submit|preventDefault={handleSubmit}>
     <input type="text" placeholder="channel name" bind:value={name} disabled={disabled} />
     <button type="submit">create</button>
 </form>
diff --git a/hi-ui/src/lib/LogIn.svelte b/hi-ui/src/lib/LogIn.svelte
index df734ee..1ec6772 100644
--- a/hi-ui/src/lib/LogIn.svelte
+++ b/hi-ui/src/lib/LogIn.svelte
@@ -7,7 +7,6 @@
     let password = '';
 
     async function handleLogin(event) {
-        event.preventDefault();
         disabled = true;
         const response = await logIn(username, password);
         if (200 <= response.status && response.status < 300) {
@@ -19,7 +18,7 @@
     }
 </script>
 
-<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" on:submit={handleLogin}>
+<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
diff --git a/hi-ui/src/lib/LogOut.svelte b/hi-ui/src/lib/LogOut.svelte
index 64d26c9..738be24 100644
--- a/hi-ui/src/lib/LogOut.svelte
+++ b/hi-ui/src/lib/LogOut.svelte
@@ -9,7 +9,6 @@
     });
 
     async function handleLogout(event) {
-        event.preventDefault();
         const response = await logOut();
         if (200 <= response.status && response.status < 300) {
             currentUser.update(() => null);
@@ -17,7 +16,7 @@
     }
 </script>
 
-<form on:submit={handleLogout}>
+<form on:submit|preventDefault={handleLogout}>
     @{user.username}
     <button
         class="border-slate-500 border-solid border-2 font-bold p-1 rounded"
diff --git a/hi-ui/src/lib/Message.svelte b/hi-ui/src/lib/Message.svelte
new file mode 100644
index 0000000..d3ecbd8
--- /dev/null
+++ b/hi-ui/src/lib/Message.svelte
@@ -0,0 +1,38 @@
+<script>
+    import { currentUser } from '../store';
+    import { deleteMessage } from '../apiServer';
+
+    export let at;
+    export let sender;
+    export let message;
+
+    let timestamp = new Date(at).toTimeString();
+
+    function handleDeleteMessage() {
+        deleteMessage(message.id);
+    }
+</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>
+    {#if sender.id === $currentUser?.id}
+        <div class="controls basis-1 hidden relative -top-3 rounded-md border-2 border-slate-600 px-1 bg-slate-300">
+            <button on:click|preventDefault={handleDeleteMessage}>&#x1F5D1;</button>
+        </div>
+    {/if}
+</div>
+
+<style>
+    div:hover .controls {
+        display: block;
+    }
+</style>
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
index 96e9577..938e467 100644
--- a/hi-ui/src/lib/MessageInput.svelte
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -1,27 +1,25 @@
 <script>
+    import { tick } from 'svelte';
     import { postToChannel } from '../apiServer';
     import { activeChannel } from '../store';
 
+    let self;
     let input;
-    let disabled = false;
-    let activeChannelId;
-
-    activeChannel.subscribe((value) => {
-        activeChannelId = value ? value.id : null;
-    });
+    $: disabled = $activeChannel == null;
 
     async function handleSubmit(event) {
-        event.preventDefault();
         disabled = true;
         // TODO try/catch:
-        await postToChannel(activeChannelId, input);
+        await postToChannel($activeChannel?.id, input);
         input = '';
         disabled = false;
+        await tick();
+        self.focus();
     }
 </script>
 
-<form on:submit={handleSubmit}>
-    <input type="text" class="border rounded px-3" bind:value={input} disabled={disabled}>
+<form on:submit|preventDefault={handleSubmit}>
+    <input type="text" class="border rounded px-3" bind:this={self} bind:value={input} disabled={disabled}>
     <button type="submit">➤</button>
 </form>
 
diff --git a/hi-ui/src/routes/+page.svelte b/hi-ui/src/routes/+page.svelte
index 646665e..66b4f8d 100644
--- a/hi-ui/src/routes/+page.svelte
+++ b/hi-ui/src/routes/+page.svelte
@@ -25,6 +25,7 @@
                 case 200:
                     currentUser.update(() => ({
                         username: response.data.login.name,
+                        id: response.data.login.id,
                     }));
                     subscribeToEvents();
                     break;
@@ -66,7 +67,7 @@
 
 <style>
     #interface {
-        height: 90%;
+        height: 89vh;
         margin: 1rem;
         display: grid;
         grid-template-columns: 18rem auto;
@@ -74,6 +75,8 @@
         grid-gap: 0.25rem;
     }
     #interface div {
+        max-height: 100%;
+        overflow: scroll;
         border: 1px solid grey;
     }
 </style>
-- 
cgit v1.2.3


From 9430825290ba345201546e35578891468da87265 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Fri, 4 Oct 2024 14:10:32 -0400
Subject: Add some Flowbite

For the styling.
---
 hi-ui/package-lock.json           | 452 +++++++++++++++++++++++++++++++-------
 hi-ui/package.json                |   3 +
 hi-ui/src/lib/MessageInput.svelte |  18 +-
 hi-ui/tailwind.config.js          |   5 +-
 4 files changed, 392 insertions(+), 86 deletions(-)

(limited to 'hi-ui/src/lib/MessageInput.svelte')

diff --git a/hi-ui/package-lock.json b/hi-ui/package-lock.json
index a183931..14e0c8a 100644
--- a/hi-ui/package-lock.json
+++ b/hi-ui/package-lock.json
@@ -19,6 +19,9 @@
 				"eslint": "^9.0.0",
 				"eslint-config-prettier": "^9.1.0",
 				"eslint-plugin-svelte": "^2.36.0",
+				"flowbite": "^2.5.2",
+				"flowbite-svelte": "^0.46.22",
+				"flowbite-svelte-icons": "^1.6.1",
 				"globals": "^15.0.0",
 				"postcss": "^8.4.47",
 				"prettier": "^3.1.1",
@@ -570,6 +573,34 @@
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			}
 		},
+		"node_modules/@floating-ui/core": {
+			"version": "1.6.8",
+			"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+			"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@floating-ui/utils": "^0.2.8"
+			}
+		},
+		"node_modules/@floating-ui/dom": {
+			"version": "1.6.11",
+			"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
+			"integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@floating-ui/core": "^1.6.0",
+				"@floating-ui/utils": "^0.2.8"
+			}
+		},
+		"node_modules/@floating-ui/utils": {
+			"version": "0.2.8",
+			"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+			"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/@humanwhocodes/module-importer": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -754,10 +785,76 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/@popperjs/core": {
+			"version": "2.11.8",
+			"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+			"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+			"dev": true,
+			"license": "MIT",
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/popperjs"
+			}
+		},
+		"node_modules/@rollup/plugin-node-resolve": {
+			"version": "15.3.0",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
+			"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@rollup/pluginutils": "^5.0.1",
+				"@types/resolve": "1.20.2",
+				"deepmerge": "^4.2.2",
+				"is-module": "^1.0.0",
+				"resolve": "^1.22.1"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^2.78.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/pluginutils": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz",
+			"integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@types/estree": "^1.0.0",
+				"estree-walker": "^2.0.2",
+				"picomatch": "^2.3.1"
+			},
+			"engines": {
+				"node": ">=14.0.0"
+			},
+			"peerDependencies": {
+				"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+			},
+			"peerDependenciesMeta": {
+				"rollup": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/@rollup/rollup-android-arm-eabi": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
-			"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
+			"integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
 			"cpu": [
 				"arm"
 			],
@@ -769,9 +866,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-android-arm64": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
-			"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
+			"integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
 			"cpu": [
 				"arm64"
 			],
@@ -783,9 +880,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-darwin-arm64": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
-			"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
+			"integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
 			"cpu": [
 				"arm64"
 			],
@@ -797,9 +894,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-darwin-x64": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
-			"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
+			"integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
 			"cpu": [
 				"x64"
 			],
@@ -811,9 +908,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
-			"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
+			"integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
 			"cpu": [
 				"arm"
 			],
@@ -825,9 +922,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm-musleabihf": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
-			"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
+			"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
 			"cpu": [
 				"arm"
 			],
@@ -839,9 +936,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm64-gnu": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
-			"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
+			"integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
 			"cpu": [
 				"arm64"
 			],
@@ -853,9 +950,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm64-musl": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
-			"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
+			"integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
 			"cpu": [
 				"arm64"
 			],
@@ -867,9 +964,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
-			"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
+			"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
 			"cpu": [
 				"ppc64"
 			],
@@ -881,9 +978,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-riscv64-gnu": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
-			"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
+			"integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
 			"cpu": [
 				"riscv64"
 			],
@@ -895,9 +992,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-s390x-gnu": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
-			"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
+			"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
 			"cpu": [
 				"s390x"
 			],
@@ -909,9 +1006,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-x64-gnu": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
-			"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
+			"integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
 			"cpu": [
 				"x64"
 			],
@@ -923,9 +1020,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-x64-musl": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
-			"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
+			"integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
 			"cpu": [
 				"x64"
 			],
@@ -937,9 +1034,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-arm64-msvc": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
-			"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
+			"integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
 			"cpu": [
 				"arm64"
 			],
@@ -951,9 +1048,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-ia32-msvc": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
-			"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
+			"integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
 			"cpu": [
 				"ia32"
 			],
@@ -965,9 +1062,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-x64-msvc": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
-			"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
+			"integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
 			"cpu": [
 				"x64"
 			],
@@ -1097,6 +1194,20 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/@types/resolve": {
+			"version": "1.20.2",
+			"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+			"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/@yr/monotone-cubic-spline": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
+			"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/acorn": {
 			"version": "8.12.1",
 			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -1184,6 +1295,22 @@
 				"node": ">= 8"
 			}
 		},
+		"node_modules/apexcharts": {
+			"version": "3.54.0",
+			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.0.tgz",
+			"integrity": "sha512-ZgI/seScffjLpwNRX/gAhIkAhpCNWiTNsdICv7qxnF0xisI23XSsaENUKIcMlyP1rbe8ECgvybDnp7plZld89A==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@yr/monotone-cubic-spline": "^1.0.3",
+				"svg.draggable.js": "^2.2.2",
+				"svg.easing.js": "^2.0.0",
+				"svg.filter.js": "^2.0.2",
+				"svg.pathmorphing.js": "^0.1.3",
+				"svg.resize.js": "^1.4.3",
+				"svg.select.js": "^3.0.1"
+			}
+		},
 		"node_modules/arg": {
 			"version": "5.0.2",
 			"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -2069,6 +2196,61 @@
 			"dev": true,
 			"license": "ISC"
 		},
+		"node_modules/flowbite": {
+			"version": "2.5.2",
+			"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz",
+			"integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@popperjs/core": "^2.9.3",
+				"flowbite-datepicker": "^1.3.0",
+				"mini-svg-data-uri": "^1.4.3"
+			}
+		},
+		"node_modules/flowbite-datepicker": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.0.tgz",
+			"integrity": "sha512-CLVqzuoE2vkUvWYK/lJ6GzT0be5dlTbH3uuhVwyB67+PjqJWABm2wv68xhBf5BqjpBxvTSQ3mrmLHpPJ2tvrSQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@rollup/plugin-node-resolve": "^15.2.3",
+				"flowbite": "^2.0.0"
+			}
+		},
+		"node_modules/flowbite-svelte": {
+			"version": "0.46.22",
+			"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.46.22.tgz",
+			"integrity": "sha512-U+YeJ3ye1OV/9d9VGff/0yuWxkv2Cdk71bvy5JJszIjfEfRvEzX5Ovjm6SHYp51B6hLwEdmMFJXPo5bTmEMGkA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@floating-ui/dom": "^1.6.11",
+				"apexcharts": "^3.54.0",
+				"flowbite": "^2.5.2",
+				"tailwind-merge": "^2.5.2"
+			},
+			"engines": {
+				"node": ">=18.0.0",
+				"pnpm": ">=8.0.0"
+			},
+			"peerDependencies": {
+				"svelte": "^3.55.1 || ^4.0.0 || ^5.0.0"
+			}
+		},
+		"node_modules/flowbite-svelte-icons": {
+			"version": "1.6.1",
+			"resolved": "https://registry.npmjs.org/flowbite-svelte-icons/-/flowbite-svelte-icons-1.6.1.tgz",
+			"integrity": "sha512-Kw/7BzA6fqlFq7tBNudwX0KVU4cbyyXcMcgHTraMwGBtvBQan0RKMbvWwqm4JZNvLGAvRv1BM2EF7rzo/oam1Q==",
+			"dev": true,
+			"license": "MIT",
+			"peerDependencies": {
+				"svelte": "^3.54.0 || ^4.0.0 || ^5.0.0",
+				"tailwind-merge": "^2.0.0",
+				"tailwindcss": "^3.3.2"
+			}
+		},
 		"node_modules/follow-redirects": {
 			"version": "1.15.9",
 			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -2379,6 +2561,13 @@
 				"node": ">=0.10.0"
 			}
 		},
+		"node_modules/is-module": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+			"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/is-number": {
 			"version": "7.0.0",
 			"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -2633,6 +2822,16 @@
 				"node": ">= 0.6"
 			}
 		},
+		"node_modules/mini-svg-data-uri": {
+			"version": "1.4.4",
+			"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+			"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+			"dev": true,
+			"license": "MIT",
+			"bin": {
+				"mini-svg-data-uri": "cli.js"
+			}
+		},
 		"node_modules/minimatch": {
 			"version": "3.1.2",
 			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3259,13 +3458,13 @@
 			}
 		},
 		"node_modules/rollup": {
-			"version": "4.21.3",
-			"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz",
-			"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==",
+			"version": "4.24.0",
+			"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
+			"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
-				"@types/estree": "1.0.5"
+				"@types/estree": "1.0.6"
 			},
 			"bin": {
 				"rollup": "dist/bin/rollup"
@@ -3275,32 +3474,25 @@
 				"npm": ">=8.0.0"
 			},
 			"optionalDependencies": {
-				"@rollup/rollup-android-arm-eabi": "4.21.3",
-				"@rollup/rollup-android-arm64": "4.21.3",
-				"@rollup/rollup-darwin-arm64": "4.21.3",
-				"@rollup/rollup-darwin-x64": "4.21.3",
-				"@rollup/rollup-linux-arm-gnueabihf": "4.21.3",
-				"@rollup/rollup-linux-arm-musleabihf": "4.21.3",
-				"@rollup/rollup-linux-arm64-gnu": "4.21.3",
-				"@rollup/rollup-linux-arm64-musl": "4.21.3",
-				"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3",
-				"@rollup/rollup-linux-riscv64-gnu": "4.21.3",
-				"@rollup/rollup-linux-s390x-gnu": "4.21.3",
-				"@rollup/rollup-linux-x64-gnu": "4.21.3",
-				"@rollup/rollup-linux-x64-musl": "4.21.3",
-				"@rollup/rollup-win32-arm64-msvc": "4.21.3",
-				"@rollup/rollup-win32-ia32-msvc": "4.21.3",
-				"@rollup/rollup-win32-x64-msvc": "4.21.3",
+				"@rollup/rollup-android-arm-eabi": "4.24.0",
+				"@rollup/rollup-android-arm64": "4.24.0",
+				"@rollup/rollup-darwin-arm64": "4.24.0",
+				"@rollup/rollup-darwin-x64": "4.24.0",
+				"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
+				"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
+				"@rollup/rollup-linux-arm64-gnu": "4.24.0",
+				"@rollup/rollup-linux-arm64-musl": "4.24.0",
+				"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
+				"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
+				"@rollup/rollup-linux-s390x-gnu": "4.24.0",
+				"@rollup/rollup-linux-x64-gnu": "4.24.0",
+				"@rollup/rollup-linux-x64-musl": "4.24.0",
+				"@rollup/rollup-win32-arm64-msvc": "4.24.0",
+				"@rollup/rollup-win32-ia32-msvc": "4.24.0",
+				"@rollup/rollup-win32-x64-msvc": "4.24.0",
 				"fsevents": "~2.3.2"
 			}
 		},
-		"node_modules/rollup/node_modules/@types/estree": {
-			"version": "1.0.5",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
-			"dev": true,
-			"license": "MIT"
-		},
 		"node_modules/run-parallel": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -3693,6 +3885,116 @@
 				"svelte": "^3.19.0 || ^4.0.0"
 			}
 		},
+		"node_modules/svg.draggable.js": {
+			"version": "2.2.2",
+			"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
+			"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.0.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.easing.js": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
+			"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": ">=2.3.x"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.filter.js": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
+			"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.2.5"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.js": {
+			"version": "2.7.1",
+			"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
+			"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/svg.pathmorphing.js": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
+			"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.4.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.resize.js": {
+			"version": "1.4.3",
+			"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
+			"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.6.5",
+				"svg.select.js": "^2.1.2"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.resize.js/node_modules/svg.select.js": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
+			"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.2.5"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/svg.select.js": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
+			"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"svg.js": "^2.6.5"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/tailwind-merge": {
+			"version": "2.5.3",
+			"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.3.tgz",
+			"integrity": "sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==",
+			"dev": true,
+			"license": "MIT",
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/dcastil"
+			}
+		},
 		"node_modules/tailwindcss": {
 			"version": "3.4.12",
 			"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz",
diff --git a/hi-ui/package.json b/hi-ui/package.json
index fb8c483..663cf81 100644
--- a/hi-ui/package.json
+++ b/hi-ui/package.json
@@ -18,6 +18,9 @@
 		"eslint": "^9.0.0",
 		"eslint-config-prettier": "^9.1.0",
 		"eslint-plugin-svelte": "^2.36.0",
+		"flowbite": "^2.5.2",
+		"flowbite-svelte": "^0.46.22",
+		"flowbite-svelte-icons": "^1.6.1",
 		"globals": "^15.0.0",
 		"postcss": "^8.4.47",
 		"prettier": "^3.1.1",
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
index 938e467..6cd732a 100644
--- a/hi-ui/src/lib/MessageInput.svelte
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -1,4 +1,7 @@
 <script>
+    import { Input, InputAddon, ButtonGroup, Button } from 'flowbite-svelte';
+    import { CaretRightSolid } from 'flowbite-svelte-icons';
+
     import { tick } from 'svelte';
     import { postToChannel } from '../apiServer';
     import { activeChannel } from '../store';
@@ -19,13 +22,10 @@
 </script>
 
 <form on:submit|preventDefault={handleSubmit}>
-    <input type="text" class="border rounded px-3" bind:this={self} bind:value={input} disabled={disabled}>
-    <button type="submit">➤</button>
+    <ButtonGroup class="w-full">
+        <Input disabled={disabled} bind:this={self} bind:value={input} />
+        <Button color="primary" class="!p-2.5" type="submit">
+            <CaretRightSolid class="w-5 h-5" />
+        </Button>
+    </ButtonGroup>
 </form>
-
-<style>
-    input[type="text"] {
-        /* TODO: percentage won't handle zoom in/out well. */
-        width: 96%;
-    }
-</style>
diff --git a/hi-ui/tailwind.config.js b/hi-ui/tailwind.config.js
index 13207cc..992b0ce 100644
--- a/hi-ui/tailwind.config.js
+++ b/hi-ui/tailwind.config.js
@@ -1,9 +1,10 @@
+import flowbitePlugin from 'flowbite/plugin';
+
 /** @type {import('tailwindcss').Config} */
 export default {
   content: ['./src/**/*.{html,js,svelte,ts}'],
   theme: {
     extend: {},
   },
-  plugins: [],
+	plugins: [flowbitePlugin]
 }
-
-- 
cgit v1.2.3


From d2af16cd8368dc52d5d8702eb0ef2cf74a33cdb9 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Sat, 5 Oct 2024 17:25:14 -0400
Subject: Remove delete-message buttons

They're badly styled and don't do anything yet anyway.
---
 hi-ui/src/lib/Message.svelte      | 9 ---------
 hi-ui/src/lib/MessageInput.svelte | 8 ++++----
 2 files changed, 4 insertions(+), 13 deletions(-)

(limited to 'hi-ui/src/lib/MessageInput.svelte')

diff --git a/hi-ui/src/lib/Message.svelte b/hi-ui/src/lib/Message.svelte
index d3ecbd8..88e47cf 100644
--- a/hi-ui/src/lib/Message.svelte
+++ b/hi-ui/src/lib/Message.svelte
@@ -7,10 +7,6 @@
     export let message;
 
     let timestamp = new Date(at).toTimeString();
-
-    function handleDeleteMessage() {
-        deleteMessage(message.id);
-    }
 </script>
 
 <div class="hover:bg-zinc-300 flex flex-row">
@@ -24,11 +20,6 @@
         <!-- TODO: this is too long and looks awful. -->
         <!-- {timestamp} -->
     </div>
-    {#if sender.id === $currentUser?.id}
-        <div class="controls basis-1 hidden relative -top-3 rounded-md border-2 border-slate-600 px-1 bg-slate-300">
-            <button on:click|preventDefault={handleDeleteMessage}>&#x1F5D1;</button>
-        </div>
-    {/if}
 </div>
 
 <style>
diff --git a/hi-ui/src/lib/MessageInput.svelte b/hi-ui/src/lib/MessageInput.svelte
index 6cd732a..9a8475c 100644
--- a/hi-ui/src/lib/MessageInput.svelte
+++ b/hi-ui/src/lib/MessageInput.svelte
@@ -1,5 +1,5 @@
 <script>
-    import { Input, InputAddon, ButtonGroup, Button } from 'flowbite-svelte';
+    import { Input, ButtonGroup, Button } from 'flowbite-svelte';
     import { CaretRightSolid } from 'flowbite-svelte-icons';
 
     import { tick } from 'svelte';
@@ -21,10 +21,10 @@
     }
 </script>
 
-<form on:submit|preventDefault={handleSubmit}>
-    <ButtonGroup class="w-full">
+<form on:submit|preventDefault={handleSubmit} class="w-full">
+    <ButtonGroup>
         <Input disabled={disabled} bind:this={self} bind:value={input} />
-        <Button color="primary" class="!p-2.5" type="submit">
+        <Button color="primary" type="submit">
             <CaretRightSolid class="w-5 h-5" />
         </Button>
     </ButtonGroup>
-- 
cgit v1.2.3

