click to show lil info panel

This commit is contained in:
2026-08-24 00:28:38 +08:00
parent 62993c977e
commit 842e119f1d
3 changed files with 244 additions and 15 deletions
+58 -15
View File
@@ -1,33 +1,76 @@
<script lang="ts">
import { onMount } from "svelte";
import { startHitboxSync } from "./hitboxes";
import Popover from "$lib/components/popover.svelte";
import { liveMetadata } from "$lib/listeners/live-metadata";
import SceneUserPopoverContent from "./scene-user-popover-content.svelte";
import { startHitboxSync } from "./hitboxes";
let selectedUserId: string | null = null;
let selectedUserX: number | null = null;
onMount(startHitboxSync);
function setPopoverOpen(
userId: string,
cursorX: number,
open: boolean,
trigger: HTMLButtonElement,
) {
selectedUserId = open ? userId : null;
selectedUserX = open
? window.innerWidth > 0
? trigger.getBoundingClientRect().left / window.innerWidth
: cursorX
: null;
}
</script>
<div class="size-full flex flex-col justify-end">
<div role="banner" class="relative h-8 w-full my-2">
{#each Object.entries($liveMetadata.cursorPositions) as [userId, cursor] (userId)}
{@const ico = $liveMetadata.foregroundApps.get(userId)?.ico}
<div class="flex size-full flex-col justify-end">
<div role="banner" class="relative my-2 h-8 w-full">
{#each Object.entries($liveMetadata.cursorPositions) as [userId, cursor], index (userId)}
{@const foregroundApp = $liveMetadata.foregroundApps.get(userId)}
{@const popoverId = `scene-user-${index}`}
{#if cursor}
{@const renderedX = selectedUserId === userId ? selectedUserX : cursor.mapped.x}
<div
class="scene-hitbox absolute bottom-0 left-0 flex flex-col gap-1 items-center transition-transform ease-linear duration-1000"
style:transform={`translateX(${cursor.mapped.x * 100}vw)`}
class="absolute bottom-0 left-0 flex flex-col items-center gap-1 transition-transform duration-1000 ease-linear"
style:transform={`translateX(${(renderedX ?? cursor.mapped.x) * 100}vw)`}
>
{#if ico}
{#if foregroundApp?.ico}
<img
src={`data:image/png;base64,${ico}`}
src={`data:image/png;base64,${foregroundApp.ico}`}
alt=""
class="size-4 object-contain"
/>
{/if}
<img
src="/fa.png"
alt=""
class="size-6"
style="filter: sepia(1) saturate(200%) hue-rotate(100deg);"
/>
<Popover
id={popoverId}
label="Show live user information"
labelledBy={`${popoverId}-title`}
open={selectedUserId === userId}
onOpenChange={(open, trigger) =>
setPopoverOpen(userId, cursor.mapped.x, open, trigger)}
triggerClass="scene-hitbox"
panelClass="scene-hitbox bottom-[calc(100%+0.75rem)] w-68"
panelStyle={`left: clamp(calc(0.5rem - ${(renderedX ?? cursor.mapped.x) * 100}vw), -8rem, calc(100vw - ${(renderedX ?? cursor.mapped.x) * 100}vw - 17.5rem))`}
>
{#snippet trigger()}
<img
src="/fa.png"
alt=""
class="size-6 sepia saturate-200 hue-rotate-[100deg]"
/>
{/snippet}
<SceneUserPopoverContent
titleId={`${popoverId}-title`}
{userId}
isLocal={userId === $liveMetadata.localId}
{cursor}
{foregroundApp}
/>
</Popover>
</div>
{/if}
{/each}
@@ -0,0 +1,64 @@
<script lang="ts">
import type { AppMeta, CursorPositions } from "$lib/bindings";
import { friends } from "$lib/listeners/friends";
import { profile } from "$lib/listeners/profile";
type Props = {
titleId: string;
userId: string;
isLocal: boolean;
cursor: CursorPositions;
foregroundApp?: AppMeta;
};
let { titleId, userId, isLocal, cursor, foregroundApp }: Props = $props();
let username = $derived(
$profile?.id === userId
? $profile.displayName
: ($friends.find((friend) => friend.id === userId)?.displayName ??
"Unknown user"),
);
function compactId(id: string) {
return id.length > 18 ? `${id.slice(0, 9)}…${id.slice(-7)}` : id;
}
</script>
<article class="w-68 overflow-hidden text-neutral-100">
<header
class="flex w-max items-center ml-2 gap-1 px-2 py-0.5 bg-neutral-800 border border-neutral-500 border-b-0"
>
<div class="min-w-0 flex-1">
<p id={titleId} class="truncate text-xs">{username}</p>
</div>
</header>
<div
class="grid gap-3 p-2 text-sm bg-neutral-900 border border-neutral-500 shadow-lg"
>
<section>
<h3 class="text-xs font-medium text-neutral-400">Currently enjoying</h3>
<div class="mt-1 flex min-w-0 items-center gap-2">
{#if foregroundApp?.ico}
<img
src={`data:image/png;base64,${foregroundApp.ico}`}
alt=""
class="size-6 shrink-0 object-contain"
/>
{/if}
<div class="min-w-0">
<p class="truncate text-xs">
{foregroundApp?.local ??
foregroundApp?.unlocal ??
"Waiting for data"}
</p>
{#if foregroundApp?.local && foregroundApp.unlocal}
<p class="truncate text-xs text-neutral-400">
{foregroundApp.unlocal}
</p>
{/if}
</div>
</div>
</section>
</div>
</article>