Skip to content

Commit cdcc5d0

Browse files
committed
[svelte] Chat
1 parent 37f2daf commit cdcc5d0

File tree

9 files changed

+6936
-3560
lines changed

9 files changed

+6936
-3560
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{{includeFile template="client/src/chat/settingsStore.ts.hbs" output="client/src/settingsStore.ts"}}
2+
{{includeFile template="client/src/chat/chatStore.ts.hbs" output="client/src/chatStore.ts"}}
3+
{{includeFile template="client/src/shared/Loading.svelte.hbs" output="client/src/Loading.svelte"}}
4+
{{includeFile template="client/src/chat/UsernameInput.svelte.hbs" output="client/src/UsernameInput.svelte"}}
5+
{{includeFile template="client/src/chat/Messages.svelte.hbs" output="client/src/Messages.svelte"}}
6+
{{includeFile template="client/src/chat/MessageInput.svelte.hbs" output="client/src/MessageInput.svelte"}}
7+
8+
<script{{#if typescript}} lang="ts"{{/if}}>
9+
import {onMount} from 'svelte';
10+
import Loading from './Loading.svelte';
11+
import MessageInput from './MessageInput.svelte';
12+
import Messages from './Messages.svelte';
13+
import UsernameInput from './UsernameInput.svelte';
14+
import {chatStoreReady} from './chatStore';
15+
import {settingsStoreReady} from './settingsStore';
16+
17+
let loading = $state(true);
18+
19+
onMount(async () => {
20+
await Promise.all([settingsStoreReady, chatStoreReady]);
21+
loading = false;
22+
});
23+
</script>
24+
25+
{#if loading}
26+
<Loading />
27+
{:else}
28+
<UsernameInput />
29+
<Messages />
30+
<MessageInput />
31+
{/if}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{includeFile template="client/src/chat/message.css.hbs" output="client/src/message.css"}}
2+
3+
<script{{#if typescript}} lang="ts"{{/if}}>
4+
import './message.css';
5+
{{#if typescript}}import type {MessageRow} from './chatStore';{{/if}}
6+
7+
let {message}{{#if typescript}}: {message: MessageRow}{{/if}} = $props();
8+
9+
const getTime = (timestamp{{#if typescript}}: number{{/if}}) =>
10+
new Date(timestamp).toLocaleTimeString();
11+
</script>
12+
13+
<div class="message">
14+
<span class="username">{message.username}:</span>
15+
<span class="text">{message.text}</span>
16+
<span class="time">{getTime(message.timestamp)}</span>
17+
</div>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{{includeFile template="client/src/shared/button.css.hbs" output="client/src/button.css"}}
2+
{{includeFile template="client/src/shared/input.css.hbs" output="client/src/input.css"}}
3+
{{includeFile template="client/src/chat/messageInput.css.hbs" output="client/src/messageInput.css"}}
4+
5+
<script{{#if typescript}} lang="ts"{{/if}}>
6+
import './button.css';
7+
import './input.css';
8+
import './messageInput.css';
9+
import {onMount} from 'svelte';
10+
import {chatStore} from './chatStore';
11+
import {settingsStore} from './settingsStore';
12+
13+
let message = $state('');
14+
let input{{#if typescript}}: HTMLInputElement | undefined{{/if}};
15+
16+
const sendMessage = () => {
17+
const text = message.trim();
18+
if (!text) {
19+
return;
20+
}
21+
22+
const username =
23+
{{#if typescript}}(settingsStore.getValue('username') as string | undefined) ?? 'Anonymous'{{else}}settingsStore.getValue('username') ?? 'Anonymous'{{/if}};
24+
25+
chatStore.addRow('messages', {
26+
username,
27+
text,
28+
timestamp: Date.now(),
29+
});
30+
31+
message = '';
32+
input?.focus();
33+
};
34+
35+
const handleSubmit = (event{{#if typescript}}: SubmitEvent{{/if}}) => {
36+
event.preventDefault();
37+
sendMessage();
38+
};
39+
40+
onMount(() => {
41+
input?.focus();
42+
});
43+
</script>
44+
45+
<form id="messageInput" onsubmit={handleSubmit}>
46+
<input
47+
bind:this={input}
48+
bind:value={message}
49+
type="text"
50+
placeholder="Type a message..."
51+
/>
52+
<button class="primary" type="submit">Send</button>
53+
</form>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{{includeFile template="client/src/chat/messages.css.hbs" output="client/src/messages.css"}}
2+
{{includeFile template="client/src/chat/Message.svelte.hbs" output="client/src/Message.svelte"}}
3+
4+
<script{{#if typescript}} lang="ts"{{/if}}>
5+
import './messages.css';
6+
import {onMount} from 'svelte';
7+
import Message from './Message.svelte';
8+
import {chatStore} from './chatStore';
9+
{{#if typescript}}import type {MessageRow} from './chatStore';{{/if}}
10+
11+
const getMessages = () =>
12+
chatStore.getSortedRowIds('messages', 'timestamp').map((id) => ({
13+
id,
14+
row:
15+
{{#if typescript}}chatStore.getRow('messages', id) as MessageRow{{else}}chatStore.getRow('messages', id){{/if}},
16+
}));
17+
18+
let messages = $state(getMessages());
19+
20+
const updateMessages = () => {
21+
messages = getMessages();
22+
};
23+
24+
onMount(() => {
25+
chatStore.addTablesListener(updateMessages);
26+
updateMessages();
27+
});
28+
</script>
29+
30+
<div id="messages">
31+
{#each messages as message (message.id)}
32+
<Message message={message.row} />
33+
{/each}
34+
</div>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{{includeFile template="client/src/shared/input.css.hbs" output="client/src/input.css"}}
2+
{{includeFile template="client/src/chat/usernameInput.css.hbs" output="client/src/usernameInput.css"}}
3+
4+
<script{{#if typescript}} lang="ts"{{/if}}>
5+
import './input.css';
6+
import './usernameInput.css';
7+
import {onMount} from 'svelte';
8+
import {settingsStore} from './settingsStore';
9+
10+
const getUsername = () =>
11+
{{#if typescript}}(settingsStore.getValue('username') as string | undefined) ?? ''{{else}}settingsStore.getValue('username') ?? ''{{/if}};
12+
13+
let username = $state(getUsername());
14+
15+
const updateUsername = () => {
16+
username = getUsername();
17+
};
18+
19+
const handleInput = (event{{#if typescript}}: Event{{/if}}) => {
20+
{{#if typescript}}
21+
const input = event.currentTarget as HTMLInputElement;
22+
const nextUsername = input.value;
23+
{{else}}
24+
const nextUsername = event.currentTarget.value;
25+
{{/if}}
26+
settingsStore.setValue('username', nextUsername);
27+
username = nextUsername;
28+
};
29+
30+
onMount(() => {
31+
settingsStore.addValueListener('username', updateUsername);
32+
updateUsername();
33+
});
34+
</script>
35+
36+
<div id="usernameInput">
37+
<label for="usernameInputField">Your name:</label>
38+
<input
39+
id="usernameInputField"
40+
type="text"
41+
value={username}
42+
placeholder="Enter your name"
43+
oninput={handleInput}
44+
/>
45+
</div>

0 commit comments

Comments
 (0)