139 lines
4.6 KiB
Svelte
139 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import { Identity } from "spacetimedb";
|
|
import type * as Types from "../../module_bindings/types";
|
|
import ThreadMessageList from "./ThreadMessageList.svelte";
|
|
import ThreadMessageInput from "./ThreadMessageInput.svelte";
|
|
import RichText from "./RichText.svelte";
|
|
import { getContext } from "svelte";
|
|
import type { ChatService } from "../services/chat.svelte";
|
|
|
|
let {
|
|
activeThreadId,
|
|
setActiveThreadId,
|
|
pendingThreadParentMessageId,
|
|
setPendingThreadParentMessageId,
|
|
activeChannelId,
|
|
activeServer: _activeServer,
|
|
isFullyAuthenticated,
|
|
users,
|
|
identity,
|
|
allThreads,
|
|
allMessages,
|
|
allImages
|
|
}: {
|
|
activeThreadId: bigint | null,
|
|
setActiveThreadId: (id: bigint | null) => void,
|
|
pendingThreadParentMessageId: bigint | null,
|
|
setPendingThreadParentMessageId: (id: bigint | null) => void,
|
|
activeChannelId: bigint | null,
|
|
activeServer: Types.Server | undefined,
|
|
isFullyAuthenticated: boolean,
|
|
users: readonly Types.User[],
|
|
identity: Identity | null,
|
|
allThreads: readonly Types.Thread[],
|
|
allMessages: readonly Types.Message[],
|
|
allImages: readonly Types.Image[]
|
|
} = $props();
|
|
|
|
const chat = getContext<ChatService>("chat");
|
|
|
|
const activeThread = $derived(allThreads.find((t) => t.id === activeThreadId));
|
|
const pendingParentMessage = $derived(allMessages.find(m => m.id === pendingThreadParentMessageId));
|
|
|
|
const threadName = $derived(activeThread ? activeThread.name : (pendingParentMessage ? `New thread: ${pendingParentMessage.text.substring(0, 20)}...` : "Thread"));
|
|
|
|
function handleClose() {
|
|
setActiveThreadId(null);
|
|
setPendingThreadParentMessageId(null);
|
|
}
|
|
|
|
const threadMessages = $derived.by(() => {
|
|
if (activeThreadId) {
|
|
return allMessages
|
|
.filter((m) => m.threadId === activeThreadId)
|
|
.sort((a, b) =>
|
|
a.sent.microsSinceUnixEpoch < b.sent.microsSinceUnixEpoch ? -1 : 1
|
|
);
|
|
}
|
|
return [];
|
|
});
|
|
</script>
|
|
|
|
{#if (activeThreadId && activeThread) || pendingThreadParentMessageId}
|
|
<div class="thread-view">
|
|
<div
|
|
class="thread-header"
|
|
style="border-bottom: 1px solid var(--background-accent); padding: 8px; display: flex; justify-content: space-between; align-items: center;"
|
|
>
|
|
<div style="display: flex; align-items: center; gap: 8px">
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<span
|
|
style="color: var(--brand); cursor: pointer; font-size: 1rem;"
|
|
onclick={handleClose}
|
|
>
|
|
<i class="fas fa-arrow-left"></i>
|
|
</span>
|
|
<span style="font-weight: bold; font-size: 0.9rem">
|
|
{threadName}
|
|
</span>
|
|
</div>
|
|
<button
|
|
class="close-btn"
|
|
onclick={handleClose}
|
|
aria-label="Close thread view"
|
|
>
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
{#if pendingParentMessage && !activeThreadId}
|
|
<div class="thread-messages-list">
|
|
<div class="message-item" style="border-bottom: 1px solid var(--background-accent); opacity: 0.8">
|
|
<div class="message-content">
|
|
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 4px">Starting thread from:</div>
|
|
<div class="message-text">
|
|
<RichText text={pendingParentMessage.text} messageId={pendingParentMessage.id} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<ThreadMessageList
|
|
{threadMessages}
|
|
{users}
|
|
{identity}
|
|
images={allImages}
|
|
/>
|
|
|
|
<div class="typing-indicator" style="margin-left: 12px; margin-top: 4px;">
|
|
{#if chat.typingUsers.length > 0}
|
|
<div class="dots">
|
|
<div class="dot"></div>
|
|
<div class="dot"></div>
|
|
<div class="dot"></div>
|
|
</div>
|
|
<span style="font-weight: bold;">
|
|
{#if chat.typingUsers.length === 1}
|
|
{chat.typingUsers[0].name || "Someone"}
|
|
{:else if chat.typingUsers.length === 2}
|
|
{chat.typingUsers[0].name || "Someone"} and {chat.typingUsers[1].name || "Someone"}
|
|
{:else if chat.typingUsers.length === 3}
|
|
{chat.typingUsers[0].name || "Someone"}, {chat.typingUsers[1].name || "Someone"} and {chat.typingUsers[2].name || "Someone"}
|
|
{:else}
|
|
Several people
|
|
{/if}
|
|
</span>
|
|
<span>{chat.typingUsers.length === 1 ? "is" : "are"} typing...</span>
|
|
{/if}
|
|
</div>
|
|
|
|
<ThreadMessageInput
|
|
{activeChannelId}
|
|
{activeThreadId}
|
|
{isFullyAuthenticated}
|
|
/>
|
|
</div>
|
|
{/if}
|