add message rendering, fix sse cancling, add user id copying
This commit is contained in:
parent
f1539bdffa
commit
3a0f096ade
6 changed files with 155 additions and 42 deletions
|
|
@ -1,22 +1,34 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, type UserWithStatus } from '$lib';
|
import { Status, type UserWithStatus } from '$lib';
|
||||||
|
|
||||||
const {
|
const { onclick, user }: { onclick?: (e: MouseEvent) => void; user: UserWithStatus } = $props();
|
||||||
onclick,
|
|
||||||
user
|
|
||||||
}: { onclick?: (e: MouseEvent) => void, user: UserWithStatus } = $props();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a {onclick} href="##" class="flex items-center gap-2">
|
<a
|
||||||
<div class="relative">
|
{onclick}
|
||||||
<img src={"https://api.dicebear.com/7.x/pixel-art/svg?seed=" + user.username} alt={user.username} class="size-6 rounded-full" />
|
oncontextmenu={async (e) => {
|
||||||
{#if user.status === Status.OFFLINE}
|
e.preventDefault();
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"></span>
|
await navigator.clipboard.writeText(user.id);
|
||||||
{:else if user.status === Status.DND}
|
}}
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"></span>
|
href="##"
|
||||||
{:else if user.status === Status.ONLINE}
|
class="flex items-center gap-2"
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"></span>
|
>
|
||||||
{/if}
|
<div class="relative">
|
||||||
</div>
|
<img
|
||||||
{user.username}
|
src={'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + user.username}
|
||||||
|
alt={user.username}
|
||||||
|
class="size-6 rounded-full"
|
||||||
|
/>
|
||||||
|
{#if user.status === Status.OFFLINE}
|
||||||
|
<span class="absolute end-0 bottom-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"
|
||||||
|
></span>
|
||||||
|
{:else if user.status === Status.DND}
|
||||||
|
<span class="absolute end-0 bottom-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"
|
||||||
|
></span>
|
||||||
|
{:else if user.status === Status.ONLINE}
|
||||||
|
<span class="absolute end-0 bottom-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"
|
||||||
|
></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{user.username}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,25 @@ import { twMerge } from 'tailwind-merge';
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
export function formatTimestamp(dateString: string) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const isToday =
|
||||||
|
date.getDate() === now.getDate() &&
|
||||||
|
date.getMonth() === now.getMonth() &&
|
||||||
|
date.getFullYear() === now.getFullYear();
|
||||||
|
|
||||||
|
const pad = (n: number) => n.toString().padStart(2, '0');
|
||||||
|
|
||||||
|
if (isToday) {
|
||||||
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||||
|
} else {
|
||||||
|
return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()}, ${pad(
|
||||||
|
date.getHours()
|
||||||
|
)}:${pad(date.getMinutes())}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
|
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { json } from '@sveltejs/kit';
|
|
||||||
import type { RequestHandler } from './$types';
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ params }) => {
|
|
||||||
const { groupOrServerId, channelId } = params;
|
|
||||||
|
|
||||||
const isGroup = !channelId;
|
|
||||||
|
|
||||||
// fake messages
|
|
||||||
const messages = Array.from({ length: 5 }, (_, i) => ({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
authorId: `user_${Math.floor(Math.random() * 10)}`,
|
|
||||||
content: isGroup ? `Group message #${i + 1}` : `Server message #${i + 1}`,
|
|
||||||
timestamp: Date.now() - Math.floor(Math.random() * 100000)
|
|
||||||
}));
|
|
||||||
|
|
||||||
return json({
|
|
||||||
type: isGroup ? 'group' : 'server',
|
|
||||||
groupId: isGroup ? groupOrServerId : null,
|
|
||||||
serverId: isGroup ? null : groupOrServerId,
|
|
||||||
channelId: channelId ?? null,
|
|
||||||
messages
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { fail, json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { GroupID, ServerID, UserID } from '$lib';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, locals }) => {
|
||||||
|
if (!locals.user) {
|
||||||
|
return new Response('No authentication', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { grp_srv_dm, channelId } = params;
|
||||||
|
if (!grp_srv_dm) {
|
||||||
|
return new Response('Missing group, server, or DM ID.', { status: 400 });
|
||||||
|
}
|
||||||
|
let messages = [];
|
||||||
|
let type = '';
|
||||||
|
|
||||||
|
if (GroupID.is(grp_srv_dm)) {
|
||||||
|
type = 'group';
|
||||||
|
messages = Array.from({ length: 5 }, (_, i) => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
authorId: `user_${Math.floor(Math.random() * 10)}`,
|
||||||
|
content: 'group message ' + (i + 1),
|
||||||
|
timestamp: Date.now() - Math.floor(Math.random() * 100000)
|
||||||
|
}));
|
||||||
|
} else if (ServerID.is(grp_srv_dm)) {
|
||||||
|
type = 'server';
|
||||||
|
|
||||||
|
if (!channelId) {
|
||||||
|
return new Response('Missing channel ID.', { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = Array.from({ length: 5 }, (_, i) => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
authorId: `user_${Math.floor(Math.random() * 10)}`,
|
||||||
|
content: 'server message ' + (i + 1),
|
||||||
|
timestamp: Date.now() - Math.floor(Math.random() * 100000)
|
||||||
|
}));
|
||||||
|
} else if (UserID.is(grp_srv_dm)) {
|
||||||
|
type = 'dms';
|
||||||
|
messages = Array.from({ length: 5 }, (_, i) => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
authorId: Math.random() > 0.5 ? locals.user.id : grp_srv_dm,
|
||||||
|
content: 'dm message ' + (i + 1),
|
||||||
|
timestamp: Date.now() - Math.floor(Math.random() * 100000)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({
|
||||||
|
type,
|
||||||
|
id: grp_srv_dm,
|
||||||
|
messages
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -66,6 +66,16 @@ export async function GET({ locals, request }) {
|
||||||
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
||||||
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.OFFLINE });
|
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.OFFLINE });
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
console.log(`SSE Client cancelled. total: ${_clients.size}`);
|
||||||
|
|
||||||
|
if (_isUserConnected(userId)) return;
|
||||||
|
|
||||||
|
if (overwrite === Status.OFFLINE) return;
|
||||||
|
|
||||||
|
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
||||||
|
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.OFFLINE });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,14 @@
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { ActionData } from './$types';
|
import type { ActionData } from './$types';
|
||||||
|
import { formatTimestamp } from '$lib/utils';
|
||||||
let errorOpen = $state(true);
|
let errorOpen = $state(true);
|
||||||
|
|
||||||
let { form, data }: { form: ActionData; data: PageServerData } = $props();
|
let { form, data }: { form: ActionData; data: PageServerData } = $props();
|
||||||
let currentPageID: (UserId | GroupId | ServerId) | null = $state(null);
|
let currentPageID: (UserId | GroupId | ServerId) | null = $state(null);
|
||||||
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
|
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
|
||||||
|
|
||||||
|
let messages = $state([]);
|
||||||
const overview_data: OverviewData = $state({
|
const overview_data: OverviewData = $state({
|
||||||
friends: [],
|
friends: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
|
|
@ -44,6 +47,20 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!currentPageID || !currentPage) return;
|
||||||
|
if (ServerID.is(currentPageID)) return;
|
||||||
|
|
||||||
|
async function getMessages() {
|
||||||
|
const req = await fetch('/api/messages/' + currentPageID);
|
||||||
|
const data = await req.json();
|
||||||
|
|
||||||
|
messages = data.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages();
|
||||||
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
async function run() {
|
async function run() {
|
||||||
overview_data.servers = data.user.servers.map((z) => {
|
overview_data.servers = data.user.servers.map((z) => {
|
||||||
|
|
@ -159,6 +176,32 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
<h1>this is like lowkirkounely the content, i should put messages and shi here</h1>
|
|
||||||
|
{#each messages as message, i (message.id)}
|
||||||
|
{#if i === 0 || messages[i - 1].authorId !== message.authorId}
|
||||||
|
<div class="flex gap-2 px-4 py-2">
|
||||||
|
<img
|
||||||
|
src={'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + message.authorId}
|
||||||
|
alt={message.authorId}
|
||||||
|
class="h-6 w-6 rounded-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="font-semibold">{message.authorId}</span>
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
{formatTimestamp(message.timestamp)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="whitespace-pre-wrap">{message.content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="ml-8 flex gap-8 px-4 py-1">
|
||||||
|
<div class="whitespace-pre-wrap">{message.content}</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
</Sidebar.Inset>
|
</Sidebar.Inset>
|
||||||
</Sidebar.Provider>
|
</Sidebar.Provider>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue