message timestamps + client avatars

This commit is contained in:
2025-06-07 14:10:52 +02:00
parent 85a223d573
commit 482285abf6
8 changed files with 161 additions and 5 deletions

View File

@@ -1,5 +1,13 @@
<script lang="ts">
import type { IMessage } from '$lib/message.svelte';
import { toSvg } from 'jdenticon';
import * as Tooltip from '$lib/components/ui/tooltip';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
dayjs.extend(relativeTime);
dayjs.extend(utc);
interface Props {
message: IMessage;
@@ -7,13 +15,40 @@
}
let { message, localSenderId }: Props = $props();
let isLocalMessage = $derived(message.senderId === localSenderId);
let timestamp = $derived(dayjs.utc(message.timestamp));
</script>
<div
class={[
'bg-accent text-accent-foreground w-fit max-w-[70%] rounded-lg px-4 py-2',
message.senderId === localSenderId && 'self-end bg-primary text-primary-foreground'
]}
class={['flex w-fit max-w-[70%] flex-row gap-2', isLocalMessage && 'flex-row-reverse self-end']}
>
<p>{message.content}</p>
<Tooltip.Provider delayDuration={0}>
<Tooltip.Root disableHoverableContent>
<Tooltip.Trigger class="h-fit self-end">
<div class="self-end overflow-hidden rounded-full border-2">
{@html toSvg(message.senderId.toString(), 32)}
</div>
</Tooltip.Trigger>
<Tooltip.Content>
<p>Client {message.senderId}</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
<div
class={[
'bg-accent text-accent-foreground rounded-lg px-4 py-2',
isLocalMessage && 'bg-primary text-primary-foreground'
]}
>
<p>{message.content}</p>
<p class={['text-sm opacity-80', isLocalMessage && 'text-right']}>
{#if timestamp.isSame(dayjs(), 'date')}
{timestamp.format('HH:mm')}
{:else}
{timestamp.format('DD.MM.YYYY HH:mm')}
{/if}
</p>
</div>
</div>

View File

@@ -0,0 +1,21 @@
import { Tooltip as TooltipPrimitive } from "bits-ui";
import Trigger from "./tooltip-trigger.svelte";
import Content from "./tooltip-content.svelte";
const Root = TooltipPrimitive.Root;
const Provider = TooltipPrimitive.Provider;
const Portal = TooltipPrimitive.Portal;
export {
Root,
Trigger,
Content,
Provider,
Portal,
//
Root as Tooltip,
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
Portal as TooltipPortal,
};

View File

@@ -0,0 +1,47 @@
<script lang="ts">
import { Tooltip as TooltipPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
sideOffset = 0,
side = "top",
children,
arrowClasses,
...restProps
}: TooltipPrimitive.ContentProps & {
arrowClasses?: string;
} = $props();
</script>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
bind:ref
data-slot="tooltip-content"
{sideOffset}
{side}
class={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-tooltip-content-transform-origin) z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs",
className
)}
{...restProps}
>
{@render children?.()}
<TooltipPrimitive.Arrow>
{#snippet child({ props })}
<div
class={cn(
"bg-primary z-50 size-2.5 rotate-45 rounded-[2px]",
"data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]",
"data-[side=bottom]:-translate-y-[calc(-50%_+_1px)] data-[side=bottom]:translate-x-1/2",
"data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2",
"data-[side=left]:translate-y-[calc(50%_-_3px)]",
arrowClasses
)}
{...props}
></div>
{/snippet}
</TooltipPrimitive.Arrow>
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { Tooltip as TooltipPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps = $props();
</script>
<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />

View File

@@ -3,6 +3,7 @@ import { getContext, setContext } from "svelte";
export interface IMessage {
senderId: number;
content: string;
timestamp: string;
};
export class MessageStore {

View File

@@ -42,6 +42,7 @@ export class ChatSocket {
}
const message: IMessage = JSON.parse(e.data);
console.log(message);
this.#store.addMessage(message);
}