feat: web UI chat render, panels, presence + analytics
This commit is contained in:
@@ -1,10 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { getPeer } from "$lib/api/endpoints";
|
||||
import type { PeerView } from "$lib/api/types";
|
||||
import {
|
||||
enqueueBackfill,
|
||||
getCurrentPresence,
|
||||
getPeer,
|
||||
} from "$lib/api/endpoints";
|
||||
import type { PeerView, PresenceSample } from "$lib/api/types";
|
||||
import Avatar from "$lib/components/ui/Avatar.svelte";
|
||||
import Button from "$lib/components/ui/Button.svelte";
|
||||
import Icon from "$lib/components/ui/Icon.svelte";
|
||||
import { peerName } from "$lib/format/peer";
|
||||
import { formatPresence } from "$lib/format/presence";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
import { chats } from "$lib/stores/chats.svelte";
|
||||
import { events } from "$lib/stores/events.svelte";
|
||||
import { toasts } from "$lib/stores/toasts.svelte";
|
||||
import { ui } from "$lib/stores/ui.svelte";
|
||||
|
||||
interface Props {
|
||||
chatId: number;
|
||||
@@ -15,6 +25,23 @@
|
||||
const isDm = $derived(chatId > 0);
|
||||
const chat = $derived(chats.byId(chatId));
|
||||
let peer = $state<PeerView | null>(null);
|
||||
let presence = $state<PresenceSample | null>(null);
|
||||
let backfilling = $state(false);
|
||||
|
||||
async function backfill() {
|
||||
if (backfilling) {
|
||||
return;
|
||||
}
|
||||
backfilling = true;
|
||||
try {
|
||||
await enqueueBackfill(chatId, true);
|
||||
toasts.success("Бэкфилл запущен");
|
||||
} catch {
|
||||
toasts.error("Не удалось запустить бэкфилл");
|
||||
} finally {
|
||||
backfilling = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (accounts.selectedId === null || !isDm) {
|
||||
@@ -39,10 +66,48 @@
|
||||
};
|
||||
});
|
||||
|
||||
const fallbackTitle = $derived(chat?.title ?? `Chat ${chatId}`);
|
||||
$effect(() => {
|
||||
if (accounts.selectedId === null || !isDm) {
|
||||
presence = null;
|
||||
return;
|
||||
}
|
||||
let active = true;
|
||||
presence = null;
|
||||
getCurrentPresence(chatId)
|
||||
.then((result) => {
|
||||
if (active) {
|
||||
presence = result;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (active) {
|
||||
presence = null;
|
||||
}
|
||||
});
|
||||
const unsub = events.subscribe((event) => {
|
||||
if (
|
||||
event.type === "presence" &&
|
||||
event.peer_id === chatId &&
|
||||
event.sample
|
||||
) {
|
||||
presence = event.sample;
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
unsub();
|
||||
};
|
||||
});
|
||||
|
||||
const fallbackTitle = $derived(
|
||||
chat?.title ?? (isDm ? "Удалённый аккаунт" : `Chat ${chatId}`)
|
||||
);
|
||||
const title = $derived(isDm && peer ? peerName(peer) : fallbackTitle);
|
||||
const subtitle = $derived.by(() => {
|
||||
if (isDm) {
|
||||
if (presence) {
|
||||
return formatPresence(presence);
|
||||
}
|
||||
if (peer?.username) {
|
||||
return `@${peer.username}`;
|
||||
}
|
||||
@@ -66,7 +131,39 @@
|
||||
/>
|
||||
<div class="info">
|
||||
<h2 class="title">{title}</h2>
|
||||
<span class="subtitle">{subtitle}</span>
|
||||
<span class="subtitle" class:online={isDm && presence?.status === "online"}>
|
||||
{subtitle}
|
||||
</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<Button
|
||||
variant="translucent"
|
||||
round
|
||||
smaller
|
||||
onclick={() => ui.openPanel("search")}
|
||||
aria-label="Поиск в чате"
|
||||
>
|
||||
<Icon name="search" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="translucent"
|
||||
round
|
||||
smaller
|
||||
onclick={() => ui.openPanel("presence")}
|
||||
aria-label="Аналитика"
|
||||
>
|
||||
<Icon name="stats" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="translucent"
|
||||
round
|
||||
smaller
|
||||
loading={backfilling}
|
||||
onclick={backfill}
|
||||
aria-label="Скачать историю"
|
||||
>
|
||||
<Icon name="cloud-download" />
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -88,6 +185,13 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
@@ -100,5 +204,9 @@
|
||||
.subtitle {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&.online {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user