minor adjustments to the control panel UI
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { friendName, friends } from "$lib/listeners/friends";
|
||||
import {
|
||||
liveMetadata,
|
||||
liveMetadataListenerError,
|
||||
} from "$lib/listeners/live-metadata";
|
||||
import { onMount } from "svelte";
|
||||
import PanelMessage from "./panel-message.svelte";
|
||||
import type { RegisterPanel } from "./types";
|
||||
|
||||
let { register }: { register: RegisterPanel } = $props();
|
||||
|
||||
const actions = { apply: async () => true, reset: () => undefined };
|
||||
onMount(() => register("activity", actions, { dirty: false, busy: false }));
|
||||
|
||||
function userIds() {
|
||||
return [
|
||||
...new Set([
|
||||
$liveMetadata.localId,
|
||||
...Object.keys($liveMetadata.cursorPositions),
|
||||
...$liveMetadata.foregroundApps.keys(),
|
||||
]),
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
function nameFor(id: string) {
|
||||
if (id === $liveMetadata.localId) return "You";
|
||||
return friendName(
|
||||
$friends.find((friend) => friend.id === id),
|
||||
"Unknown friend",
|
||||
);
|
||||
}
|
||||
|
||||
function compactId(id: string) {
|
||||
return id.length > 18 ? `${id.slice(0, 9)}…${id.slice(-6)}` : id;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full min-h-0 flex-col gap-2">
|
||||
{#if $liveMetadataListenerError}
|
||||
<PanelMessage kind="error">{$liveMetadataListenerError}</PanelMessage>
|
||||
{:else if userIds().length === 0}
|
||||
<PanelMessage
|
||||
>Waiting for activity data. This usually appears after Wyd has started
|
||||
monitoring.</PanelMessage
|
||||
>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="min-h-0 flex-1 overflow-y-auto border border-base-300 bg-base-100"
|
||||
>
|
||||
{#each userIds() as userId (userId)}
|
||||
{@const cursor = $liveMetadata.cursorPositions[userId]}
|
||||
{@const app = $liveMetadata.foregroundApps.get(userId)}
|
||||
<article class="border-b border-base-300 p-2 last:border-b-0">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if app?.ico}
|
||||
<img
|
||||
class="pixelated size-8 border border-base-300 bg-base-200 p-0.5"
|
||||
src={`data:image/png;base64,${app.ico}`}
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="grid size-8 shrink-0 place-content-center border border-base-300 bg-base-200 text-sm"
|
||||
aria-hidden="true"
|
||||
>
|
||||
▣
|
||||
</div>
|
||||
{/if}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<strong class="truncate text-xs">{nameFor(userId)}</strong>
|
||||
{#if userId === $liveMetadata.localId}<span class="badge badge-xs"
|
||||
>Local</span
|
||||
>{/if}
|
||||
</div>
|
||||
<p
|
||||
class="truncate font-mono text-[9px] text-base-content/55"
|
||||
title={userId}
|
||||
>
|
||||
{compactId(userId)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl
|
||||
class="mt-2 grid grid-cols-[5rem_1fr] gap-x-2 gap-y-0.5 text-[10px]"
|
||||
>
|
||||
<dt class="text-base-content/60">Foreground app</dt>
|
||||
<dd class="truncate">
|
||||
{app?.local ?? app?.unlocal ?? "Not available"}
|
||||
</dd>
|
||||
<dt class="text-base-content/60">Cursor</dt>
|
||||
<dd>
|
||||
{cursor
|
||||
? `${Math.round(cursor.raw.x)}, ${Math.round(cursor.raw.y)} px`
|
||||
: "Not available"}
|
||||
</dd>
|
||||
<dt class="text-base-content/60">Mapped</dt>
|
||||
<dd>
|
||||
{cursor
|
||||
? `${cursor.mapped.x.toFixed(3)}, ${cursor.mapped.y.toFixed(3)}`
|
||||
: "Not available"}
|
||||
</dd>
|
||||
</dl>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,62 +109,57 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1">
|
||||
{#if error || $sceneConfigurationListenerError || $profileListenerError}
|
||||
<PanelMessage kind="error">
|
||||
{error || $sceneConfigurationListenerError || $profileListenerError}
|
||||
</PanelMessage>
|
||||
{/if}
|
||||
|
||||
<fieldset class="fieldset border border-base-300 bg-base-100 p-3">
|
||||
<legend class="fieldset-legend px-1">Puppets</legend>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-[minmax(0,3fr)_minmax(0,4fr)] items-center gap-3"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="aspect-square min-w-0">
|
||||
{#if $profile}
|
||||
<div
|
||||
class="border border-primary relative shadow-[inset_0_0_10px] bg-primary/5 shadow-primary card"
|
||||
>
|
||||
<div class="grid grid-cols-[minmax(0,3fr)_minmax(0,4fr)] items-center gap-3">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="aspect-square min-w-0 mt-2">
|
||||
{#if $profile}
|
||||
<div
|
||||
class="border border-primary relative shadow-[inset_0_0_10px] bg-primary/5 shadow-primary card"
|
||||
>
|
||||
<div class="size-full absolute bg-gridded opacity-25 inset-0"></div>
|
||||
<div class="size-full absolute">
|
||||
<div
|
||||
class="size-full absolute bg-gridded opacity-25 inset-0"
|
||||
></div>
|
||||
<div class="size-full absolute">
|
||||
<div
|
||||
class="flex flex-row size-full items-end justify-between text-[10px] text-primary p-1"
|
||||
>
|
||||
<div class="text-start flex flex-col">
|
||||
<p>Scale</p>
|
||||
<p>Opacity</p>
|
||||
</div>
|
||||
<div class="text-end flex flex-col">
|
||||
<p>{(puppetScale * 100).toFixed(0)}%</p>
|
||||
<p>{(puppetOpacity * 100).toFixed(0)}%</p>
|
||||
</div>
|
||||
class="flex flex-row size-full items-end justify-between text-[10px] text-primary p-1"
|
||||
>
|
||||
<div class="text-start flex flex-col">
|
||||
<p>Scale</p>
|
||||
<p>Opacity</p>
|
||||
</div>
|
||||
<div class="text-end flex flex-col">
|
||||
<p>{(puppetScale * 100).toFixed(0)}%</p>
|
||||
<p>{(puppetOpacity * 100).toFixed(0)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-full z-10">
|
||||
<PuppetPreview
|
||||
userId={$profile.id}
|
||||
skinHash={$profile.skinHash}
|
||||
skinSource={skinPreviewUrl}
|
||||
scale={puppetScale}
|
||||
opacity={puppetOpacity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="skeleton aspect-square size-full"
|
||||
aria-label="Loading puppet preview"
|
||||
></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="size-full z-10">
|
||||
<PuppetPreview
|
||||
userId={$profile.id}
|
||||
skinHash={$profile.skinHash}
|
||||
skinSource={skinPreviewUrl}
|
||||
scale={puppetScale}
|
||||
opacity={puppetOpacity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="skeleton aspect-square size-full"
|
||||
aria-label="Loading puppet preview"
|
||||
></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 space-y-2">
|
||||
<div class="min-w-0 space-y-2">
|
||||
<fieldset class="fieldset border border-base-300 bg-base-100 p-3 pt-1">
|
||||
<legend class="fieldset-legend px-1">All puppets</legend>
|
||||
<div>
|
||||
<label class="fieldset-label" for="puppet-scale">Scale</label>
|
||||
<input
|
||||
@@ -196,98 +191,93 @@
|
||||
aria-describedby="puppet-opacity-help"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="fieldset-label" for="puppet-skin">Custom Skin</label>
|
||||
<div>
|
||||
<button
|
||||
class="btn w-full"
|
||||
type="button"
|
||||
disabled={!$profile || busy}
|
||||
onclick={() => skinInput?.click()}
|
||||
>
|
||||
Choose file
|
||||
</button>
|
||||
<input
|
||||
id="skin-file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="image/png"
|
||||
bind:this={skinInput}
|
||||
onchange={(event) =>
|
||||
selectSkinFile(event.currentTarget.files?.[0] ?? null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div>
|
||||
<button
|
||||
class="btn w-full"
|
||||
type="button"
|
||||
disabled={!$profile || busy}
|
||||
onclick={() => skinInput?.click()}
|
||||
>
|
||||
Choose a custom skin
|
||||
</button>
|
||||
<input
|
||||
id="skin-file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="image/png"
|
||||
bind:this={skinInput}
|
||||
onchange={(event) =>
|
||||
selectSkinFile(event.currentTarget.files?.[0] ?? null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="mt-4">
|
||||
<legend class="fieldset-label mb-2">Movement</legend>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "free"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
</div>
|
||||
<fieldset class="fieldset border border-base-300 bg-base-100 p-3 pt-1">
|
||||
<legend class="fieldset-legend px-1">Puppets reposition</legend>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "free"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-free.svg"
|
||||
alt="Puppets moving freely across the full viewport"
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "free"}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "free"}
|
||||
class:text-primary={puppetMovementMode === "free"}
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-free.svg"
|
||||
alt="Puppets moving freely across the full viewport"
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "free"}
|
||||
<span class="text-xs font-medium">Freeroam around</span>
|
||||
<input
|
||||
class="hidden"
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="free"
|
||||
checked={puppetMovementMode === "free"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "free";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "free"}
|
||||
class:text-primary={puppetMovementMode === "free"}
|
||||
>
|
||||
<span class="text-xs font-medium">Freeroam around</span>
|
||||
<input
|
||||
class="hidden"
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="free"
|
||||
checked={puppetMovementMode === "free"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "free";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "bottom"}
|
||||
class:text-primary={puppetMovementMode === "bottom"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "bottom"}
|
||||
class:text-primary={puppetMovementMode === "bottom"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-bottom.svg"
|
||||
alt="Puppets moving along the bottom of the viewport"
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "bottom"}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "bottom"}
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-bottom.svg"
|
||||
alt="Puppets moving along the bottom of the viewport"
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "bottom"}
|
||||
<span class="text-xs font-medium">Stay on the ground</span>
|
||||
<input
|
||||
class="hidden"
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="bottom"
|
||||
checked={puppetMovementMode === "bottom"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "bottom";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "bottom"}
|
||||
>
|
||||
<span class="text-xs font-medium">Stay on the ground</span>
|
||||
<input
|
||||
class="hidden"
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="bottom"
|
||||
checked={puppetMovementMode === "bottom"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "bottom";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import ActivityPanel from "$lib/components/control-panel/activity-panel.svelte";
|
||||
import FriendsPanel from "$lib/components/control-panel/friends-panel.svelte";
|
||||
import GeneralPanel from "$lib/components/control-panel/general-panel.svelte";
|
||||
import NetworkPanel from "$lib/components/control-panel/network-panel.svelte";
|
||||
@@ -11,22 +10,20 @@
|
||||
} from "$lib/components/control-panel/types";
|
||||
|
||||
const tabs = [
|
||||
{ id: "general", label: "General" },
|
||||
{ id: "scene", label: "Scene" },
|
||||
{ id: "general", label: "Account" },
|
||||
{ id: "friends", label: "Friends" },
|
||||
{ id: "network", label: "Network" },
|
||||
{ id: "activity", label: "Activity" },
|
||||
] as const;
|
||||
type TabId = (typeof tabs)[number]["id"];
|
||||
|
||||
let activeTab = $state<TabId>("general");
|
||||
let activeTab = $state<TabId>("scene");
|
||||
let actions: Partial<Record<TabId, PanelActions>> = {};
|
||||
let panelStates = $state<Record<TabId, PanelState>>({
|
||||
general: { dirty: false, busy: false },
|
||||
scene: { dirty: false, busy: false },
|
||||
friends: { dirty: false, busy: false },
|
||||
network: { dirty: false, busy: false },
|
||||
activity: { dirty: false, busy: false },
|
||||
});
|
||||
let anyDirty = $derived(tabs.some((tab) => panelStates[tab.id].dirty));
|
||||
let anyBusy = $derived(tabs.some((tab) => panelStates[tab.id].busy));
|
||||
@@ -87,7 +84,7 @@
|
||||
type="radio"
|
||||
name="control_panel_tabs"
|
||||
class="tab text-xs"
|
||||
aria-label={`${tab.label}${panelStates[tab.id].dirty ? " *" : ""}`}
|
||||
aria-label={tab.label}
|
||||
value={tab.id}
|
||||
bind:group={activeTab}
|
||||
/>
|
||||
@@ -106,7 +103,7 @@
|
||||
{:else if tab.id === "network"}
|
||||
<NetworkPanel {register} />
|
||||
{:else}
|
||||
<ActivityPanel {register} />
|
||||
<p>You're not supposed to see this 👀</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user