From 4401dce2b5545ce8117818812d8e3c8919f5f7fd Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 10 Oct 2024 21:05:48 -0400 Subject: Remove redundancy in `hi-ui` directory name. --- ui/src/app.css | 3 + ui/src/app.html | 12 +++ ui/src/lib/apiServer.js | 101 +++++++++++++++++++++++++ ui/src/lib/components/ActiveChannel.svelte | 27 +++++++ ui/src/lib/components/Channel.svelte | 21 +++++ ui/src/lib/components/ChannelList.svelte | 18 +++++ ui/src/lib/components/CreateChannelForm.svelte | 23 ++++++ ui/src/lib/components/LogIn.svelte | 35 +++++++++ ui/src/lib/components/LogOut.svelte | 22 ++++++ ui/src/lib/components/Message.svelte | 33 ++++++++ ui/src/lib/components/MessageInput.svelte | 30 ++++++++ ui/src/lib/index.js | 1 + ui/src/lib/store.js | 10 +++ ui/src/lib/store/channels.js | 71 +++++++++++++++++ ui/src/lib/store/logins.js | 22 ++++++ ui/src/lib/store/messages.js | 44 +++++++++++ ui/src/routes/(app)/+layout.svelte | 89 ++++++++++++++++++++++ ui/src/routes/(app)/+page.svelte | 0 ui/src/routes/(app)/ch/[channel]/+page.svelte | 17 +++++ ui/src/routes/+layout.js | 1 + ui/src/routes/+layout.svelte | 30 ++++++++ 21 files changed, 610 insertions(+) create mode 100644 ui/src/app.css create mode 100644 ui/src/app.html create mode 100644 ui/src/lib/apiServer.js create mode 100644 ui/src/lib/components/ActiveChannel.svelte create mode 100644 ui/src/lib/components/Channel.svelte create mode 100644 ui/src/lib/components/ChannelList.svelte create mode 100644 ui/src/lib/components/CreateChannelForm.svelte create mode 100644 ui/src/lib/components/LogIn.svelte create mode 100644 ui/src/lib/components/LogOut.svelte create mode 100644 ui/src/lib/components/Message.svelte create mode 100644 ui/src/lib/components/MessageInput.svelte create mode 100644 ui/src/lib/index.js create mode 100644 ui/src/lib/store.js create mode 100644 ui/src/lib/store/channels.js create mode 100644 ui/src/lib/store/logins.js create mode 100644 ui/src/lib/store/messages.js create mode 100644 ui/src/routes/(app)/+layout.svelte create mode 100644 ui/src/routes/(app)/+page.svelte create mode 100644 ui/src/routes/(app)/ch/[channel]/+page.svelte create mode 100644 ui/src/routes/+layout.js create mode 100644 ui/src/routes/+layout.svelte (limited to 'ui/src') diff --git a/ui/src/app.css b/ui/src/app.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/ui/src/app.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/ui/src/app.html b/ui/src/app.html new file mode 100644 index 0000000..63eb917 --- /dev/null +++ b/ui/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/ui/src/lib/apiServer.js b/ui/src/lib/apiServer.js new file mode 100644 index 0000000..f6d6148 --- /dev/null +++ b/ui/src/lib/apiServer.js @@ -0,0 +1,101 @@ +import axios from 'axios'; +import { activeChannel, channelsList, logins, messages } from '$lib/store'; + +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 createChannel(name) { + return apiServer.post('/channels', { name }); +} + +export async function postToChannel(channelId, body) { + return apiServer.post(`/channels/${channelId}`, { body }); +} + +export async function deleteMessage(messageId) { + // TODO +} + +export function subscribeToEvents(resume_point) { + const eventsUrl = new URL('/api/events', window.location); + eventsUrl.searchParams.append('resume_point', resume_point); + const evtSource = new EventSource(eventsUrl.toString()); + // 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); + + switch (data.type) { + case 'login': + onLoginEvent(data); + break; + case 'channel': + onChannelEvent(data); + break; + case 'message': + onMessageEvent(data); + break; + } + } +} + +function onLoginEvent(data) { + switch (data.event) { + case 'created': + logins.update((value) => value.addLogin(data.id, data.name)) + break; + } +} + +function onChannelEvent(data) { + switch (data.event) { + case 'created': + channelsList.update((value) => value.addChannel(data.id, data.name)) + break; + case 'deleted': + activeChannel.update((value) => value.deleteChannel(data.id)); + channelsList.update((value) => value.deleteChannel(data.id)); + messages.update((value) => value.deleteChannel(data.id)); + break; + } +} + +function onMessageEvent(data) { + switch (data.event) { + case 'sent': + messages.update((value) => value.addMessage(data.channel, data.id, data.at, data.sender, data.body)); + break; + case 'deleted': + messages.update((value) => value.deleteMessage(data.id)); + break; + } +} 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 @@ + + +
+ {#each messageList as message} +
+ +
+ {/each} +
+ + 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 @@ + + +
  • + + # + {name} + +
  • 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 @@ + + + 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 @@ + + +
    + + +
    + + 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 @@ + + +
    +
    + + + +
    +
    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 @@ + + +
    + @{$currentUser.username} + +
    + + 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 @@ + + +
    + + + @{name}: + + {at} +
    + +
    +
    + + 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 @@ + + +
    + + +
    diff --git a/ui/src/lib/index.js b/ui/src/lib/index.js new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/ui/src/lib/index.js @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/ui/src/lib/store.js b/ui/src/lib/store.js new file mode 100644 index 0000000..b964b4b --- /dev/null +++ b/ui/src/lib/store.js @@ -0,0 +1,10 @@ +import { writable } from 'svelte/store'; +import { ActiveChannel, Channels } from '$lib/store/channels'; +import { Messages } from '$lib/store/messages'; +import { Logins } from '$lib/store/logins'; + +export const currentUser = writable(null); +export const activeChannel = writable(new ActiveChannel()); +export const logins = writable(new Logins()); +export const channelsList = writable(new Channels()); +export const messages = writable(new Messages()); diff --git a/ui/src/lib/store/channels.js b/ui/src/lib/store/channels.js new file mode 100644 index 0000000..bb6c86c --- /dev/null +++ b/ui/src/lib/store/channels.js @@ -0,0 +1,71 @@ +export class Channels { + constructor() { + this.channels = []; + } + + setChannels(channels) { + this.channels = [...channels]; + this.sort(); + return this; + } + + addChannel(id, name) { + this.channels = [...this.channels, { id, name }]; + this.sort(); + return this; + } + + deleteChannel(id) { + const channelIndex = this.channels.map((e) => e.id).indexOf(id); + if (channelIndex !== -1) { + this.channels.splice(channelIndex, 1); + } + return this; + } + + sort() { + this.channels.sort((a, b) => { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } + return 0; + }); + } +} + +export class ActiveChannel { + constructor() { + this.channel = null; + } + + isSet() { + return this.channel !== null; + } + + get() { + return this.channel; + } + + is(id) { + return this.channel === id; + } + + set(id) { + this.channel = id; + return this; + } + + deleteChannel(id) { + if (this.is(id)) { + return this.clear(); + } + return this; + } + + clear() { + this.channel = null; + return this; + } +} diff --git a/ui/src/lib/store/logins.js b/ui/src/lib/store/logins.js new file mode 100644 index 0000000..5b45206 --- /dev/null +++ b/ui/src/lib/store/logins.js @@ -0,0 +1,22 @@ +export class Logins { + constructor() { + this.logins = {}; + } + + addLogin(id, name) { + this.logins[id] = name; + return this; + } + + setLogins(logins) { + this.logins = {}; + for (let { id, name } of logins) { + this.addLogin(id, name); + } + return this; + } + + get(id) { + return this.logins[id]; + } +} diff --git a/ui/src/lib/store/messages.js b/ui/src/lib/store/messages.js new file mode 100644 index 0000000..931b8fb --- /dev/null +++ b/ui/src/lib/store/messages.js @@ -0,0 +1,44 @@ +export class Messages { + constructor() { + this.channels = {}; + } + + inChannel(channel) { + return this.channels[channel] = (this.channels[channel] || []); + } + + addMessage(channel, id, at, sender, body) { + this.updateChannel(channel, (messages) => [...messages, { id, at, sender, body }]); + return this; + } + + setMessages(messages) { + this.channels = {}; + for (let { channel, id, at, sender, body } of messages) { + this.inChannel(channel).push({ id, at, sender, body, }); + } + for (let channel in this.channels) { + this.channels[channel].sort((a, b) => a.at - b.at); + } + return this; + } + + + deleteMessage(message) { + for (let channel in this.channels) { + this.updateChannel(channel, (messages) => messages.filter((msg) => msg.id != message)); + } + return this; + } + + deleteChannel(id) { + delete this.channels[id]; + return this; + } + + updateChannel(channel, callback) { + let messages = callback(this.inChannel(channel)); + messages.sort((a, b) => a.at - b.at); + this.channels[channel] = messages; + } +} diff --git a/ui/src/routes/(app)/+layout.svelte b/ui/src/routes/(app)/+layout.svelte new file mode 100644 index 0000000..f8744c1 --- /dev/null +++ b/ui/src/routes/(app)/+layout.svelte @@ -0,0 +1,89 @@ + + +{#if loading} +

    Loading…

    +{:else if user != null} +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +{:else} + +{/if} + + diff --git a/ui/src/routes/(app)/+page.svelte b/ui/src/routes/(app)/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/routes/(app)/ch/[channel]/+page.svelte b/ui/src/routes/(app)/ch/[channel]/+page.svelte new file mode 100644 index 0000000..ef439d0 --- /dev/null +++ b/ui/src/routes/(app)/ch/[channel]/+page.svelte @@ -0,0 +1,17 @@ + + + diff --git a/ui/src/routes/+layout.js b/ui/src/routes/+layout.js new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/ui/src/routes/+layout.js @@ -0,0 +1 @@ +export const ssr = false; diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte new file mode 100644 index 0000000..7b99d62 --- /dev/null +++ b/ui/src/routes/+layout.svelte @@ -0,0 +1,30 @@ + + +
    + + 🌳 + understory + + {#if $currentUser} + + {/if} + + + + +
    + + -- cgit v1.2.3