extracted event listeners out of debug components into dedicated files

This commit is contained in:
2026-08-22 00:44:41 +08:00
parent 77ec683654
commit 7bb95dc437
12 changed files with 227 additions and 177 deletions
+8 -28
View File
@@ -1,40 +1,20 @@
<script lang="ts">
import { onMount } from "svelte";
import { commands, events, type ConnectionStatus } from "$lib/bindings";
let statuses: ConnectionStatus[] = [];
let error = "";
onMount(() => {
const unlisten = events.networkStatusChanged.listen((event) => {
statuses = event.payload.statuses;
});
commands
.listStatuses()
.then((current) => {
statuses = current;
})
.catch((err) => {
error = String(err);
});
return () => {
unlisten.then((stop) => stop());
};
});
import {
connectionStatuses,
connectionStatusesListenerError,
} from "$lib/listeners/connection-status";
</script>
<section>
<h1>Connections</h1>
{#if error}
<p aria-live="polite">{error}</p>
{:else if statuses.length === 0}
{#if $connectionStatusesListenerError}
<p aria-live="polite">{$connectionStatusesListenerError}</p>
{:else if $connectionStatuses.length === 0}
<p>No remote connections configured.</p>
{:else}
<ul aria-live="polite">
{#each statuses as status (status.remoteId)}
{#each $connectionStatuses as status (status.remoteId)}
<li>
<strong>{status.name ?? status.address}</strong>: {status.state}
{#if status.name}
+6 -21
View File
@@ -1,24 +1,9 @@
<script lang="ts">
import { onMount } from "svelte";
import { commands, events, type User } from "$lib/bindings";
import { commands } from "$lib/bindings";
import { friends, friendsListenerError } from "$lib/listeners/friends";
let friends: User[] = [];
let error = "";
onMount(() => {
const unlisten = events.friendsChanged.listen((event) => {
friends = event.payload.friends;
});
commands.listFriends().catch((err) => {
error = String(err);
});
return () => {
unlisten.then((stop) => stop());
};
});
async function createFriend(event: SubmitEvent) {
const form = event.currentTarget as HTMLFormElement;
const data = new FormData(form);
@@ -68,15 +53,15 @@
<button type="submit">Create friend</button>
</form>
{#if error}
<p>{error}</p>
{#if error || $friendsListenerError}
<p>{error || $friendsListenerError}</p>
{/if}
{#if friends.length === 0}
{#if $friends.length === 0}
<p>No friends saved.</p>
{:else}
<ul>
{#each friends as friend (friend.id)}
{#each $friends as friend (friend.id)}
<li>
<span>{friend.displayName} ({friend.id})</span>
<button type="button" onclick={() => deleteFriend(friend.id)}>Delete</button>
+11 -72
View File
@@ -1,28 +1,15 @@
<script lang="ts">
import { onMount } from "svelte";
import {
commands,
events,
type AppMeta,
type CursorPositions,
} from "$lib/bindings";
let localId = "";
let cursorPositions: Partial<Record<string, CursorPositions>> = {};
let foregroundApps = new Map<string, AppMeta>();
let pendingLocalForegroundApp: AppMeta | null = null;
let error = "";
function updateForegroundApp(userId: string, meta: AppMeta) {
foregroundApps = new Map(foregroundApps).set(userId, meta);
}
liveMetadata,
liveMetadataListenerError,
} from "$lib/listeners/live-metadata";
function liveUserIds() {
return [
...new Set([
localId,
...Object.keys(cursorPositions),
...foregroundApps.keys(),
$liveMetadata.localId,
...Object.keys($liveMetadata.cursorPositions),
...$liveMetadata.foregroundApps.keys(),
]),
].filter(Boolean);
}
@@ -30,69 +17,21 @@
function compactId(id: string) {
return id.length > 16 ? `${id.slice(0, 8)}…${id.slice(-6)}` : id;
}
onMount(() => {
let disposed = false;
let stopListeners: (() => void)[] = [];
async function initialize() {
const listeners = await Promise.all([
events.cursorPositionChanged.listen((event) => {
if (!disposed) cursorPositions = event.payload.positions;
}),
events.foregroundAppChanged.listen((event) => {
if (disposed) return;
if (localId) {
updateForegroundApp(localId, event.payload.meta);
} else {
pendingLocalForegroundApp = event.payload.meta;
}
}),
events.friendForegroundAppChanged.listen((event) => {
if (!disposed) {
updateForegroundApp(event.payload.friendId, event.payload.meta);
}
}),
]);
if (disposed) {
listeners.forEach((stop) => stop());
return;
}
stopListeners = listeners;
localId = await commands.getPublicKey();
if (disposed) return;
if (pendingLocalForegroundApp) {
updateForegroundApp(localId, pendingLocalForegroundApp);
pendingLocalForegroundApp = null;
}
}
initialize().catch((err) => {
if (!disposed) error = String(err);
});
return () => {
disposed = true;
stopListeners.forEach((stop) => stop());
};
});
</script>
<section>
<h1>Live metadata</h1>
{#if error}
<p>{error}</p>
{#if $liveMetadataListenerError}
<p>{$liveMetadataListenerError}</p>
{:else if liveUserIds().length === 0}
<p>Waiting for live metadata...</p>
{:else}
{#each liveUserIds() as userId (userId)}
{@const cursor = cursorPositions[userId]}
{@const foregroundApp = foregroundApps.get(userId)}
{@const cursor = $liveMetadata.cursorPositions[userId]}
{@const foregroundApp = $liveMetadata.foregroundApps.get(userId)}
<article>
<h2>{userId === localId ? "Local user" : "Remote user"}</h2>
<h2>{userId === $liveMetadata.localId ? "Local user" : "Remote user"}</h2>
<p title={userId}>ID: {compactId(userId)}</p>
<h3>Cursor</h3>
+7 -35
View File
@@ -1,39 +1,11 @@
<script lang="ts">
import { onMount } from "svelte";
import { commands, events, type User } from "$lib/bindings";
import { commands } from "$lib/bindings";
import { profile, profileListenerError } from "$lib/listeners/profile";
let profile: User | null = null;
let displayName = "";
let error = "";
onMount(() => {
let stop: (() => void) | undefined;
let disposed = false;
async function initialize() {
const unlisten = await events.profileChanged.listen((event) => {
profile = event.payload.profile;
displayName = event.payload.profile.displayName;
});
if (disposed) {
unlisten();
return;
}
stop = unlisten;
await commands.getProfile();
}
initialize().catch((err) => {
error = String(err);
});
return () => {
disposed = true;
stop?.();
};
});
$: displayName = $profile?.displayName ?? "";
async function updateProfile(event: SubmitEvent) {
event.preventDefault();
@@ -50,8 +22,8 @@
<section>
<h1>Profile</h1>
{#if profile}
<p>Public key: {profile.id}</p>
{#if $profile}
<p>Public key: {$profile.id}</p>
{:else}
<p>Loading profile...</p>
{/if}
@@ -64,7 +36,7 @@
<button type="submit">Save profile</button>
</form>
{#if error}
<p>{error}</p>
{#if error || $profileListenerError}
<p>{error || $profileListenerError}</p>
{/if}
</section>
+6 -21
View File
@@ -1,25 +1,10 @@
<script lang="ts">
import { onMount } from "svelte";
import { commands, events, type Remote, type RemoteInput } from "$lib/bindings";
import { commands, type RemoteInput } from "$lib/bindings";
import { remotes, remotesListenerError } from "$lib/listeners/remotes";
let remotes: Remote[] = [];
let editingId: string | null = null;
let error = "";
onMount(() => {
const unlisten = events.remotesChanged.listen((event) => {
remotes = event.payload.remotes;
});
commands.listRemotes().catch((err) => {
error = String(err);
});
return () => {
unlisten.then((stop) => stop());
};
});
function remoteInput(form: HTMLFormElement): RemoteInput {
const data = new FormData(form);
const name = String(data.get("name") ?? "").trim();
@@ -98,15 +83,15 @@
<button type="submit">Create remote</button>
</form>
{#if error}
<p aria-live="polite">{error}</p>
{#if error || $remotesListenerError}
<p aria-live="polite">{error || $remotesListenerError}</p>
{/if}
{#if remotes.length === 0}
{#if $remotes.length === 0}
<p>No remotes saved.</p>
{:else}
<ul>
{#each remotes as remote (remote.id)}
{#each $remotes as remote (remote.id)}
<li>
{#if editingId === remote.id}
<form onsubmit={(event) => updateRemote(event, remote.id)}>
+19
View File
@@ -0,0 +1,19 @@
import { writable } from "svelte/store";
import { commands, events, type ConnectionStatus } from "$lib/bindings";
export const connectionStatuses = writable<ConnectionStatus[]>([]);
export const connectionStatusesListenerError = writable("");
export async function initConnectionStatusesListener() {
const unlisten = await events.networkStatusChanged.listen((event) => {
connectionStatuses.set(event.payload.statuses);
});
try {
connectionStatuses.set(await commands.listStatuses());
} catch (error) {
connectionStatusesListenerError.set(String(error));
}
return unlisten;
}
+19
View File
@@ -0,0 +1,19 @@
import { writable } from "svelte/store";
import { commands, events, type User } from "$lib/bindings";
export const friends = writable<User[]>([]);
export const friendsListenerError = writable("");
export async function initFriendsListener() {
const unlisten = await events.friendsChanged.listen((event) => {
friends.set(event.payload.friends);
});
try {
friends.set(await commands.listFriends());
} catch (error) {
friendsListenerError.set(String(error));
}
return unlisten;
}
+43
View File
@@ -0,0 +1,43 @@
import { initConnectionStatusesListener } from "./connection-status";
import { initFriendsListener } from "./friends";
import { initLiveMetadataListeners } from "./live-metadata";
import { initProfileListener } from "./profile";
import { initRemotesListener } from "./remotes";
type Unlisten = () => void;
export function initAppListeners(): Unlisten {
let disposed = false;
let unlisteners: Unlisten[] = [];
Promise.allSettled([
initFriendsListener(),
initRemotesListener(),
initProfileListener(),
initConnectionStatusesListener(),
initLiveMetadataListeners(),
])
.then((results) => {
const listeners = results.flatMap((result) =>
result.status === "fulfilled" ? [result.value] : [],
);
results.forEach((result) => {
if (result.status === "rejected") {
console.error("failed to initialize app listener", result.reason);
}
});
if (disposed) {
listeners.forEach((unlisten) => unlisten());
} else {
unlisteners = listeners;
}
});
return () => {
disposed = true;
unlisteners.forEach((unlisten) => unlisten());
unlisteners = [];
};
}
+65
View File
@@ -0,0 +1,65 @@
import { writable } from "svelte/store";
import {
commands,
events,
type AppMeta,
type CursorPositions,
} from "$lib/bindings";
export type LiveMetadata = {
localId: string;
cursorPositions: Partial<Record<string, CursorPositions>>;
foregroundApps: Map<string, AppMeta>;
};
export const liveMetadata = writable<LiveMetadata>({
localId: "",
cursorPositions: {},
foregroundApps: new Map(),
});
export const liveMetadataListenerError = writable("");
export async function initLiveMetadataListeners() {
let localId = "";
let pendingLocalForegroundApp: AppMeta | null = null;
const updateForegroundApp = (userId: string, meta: AppMeta) => {
liveMetadata.update((current) => ({
...current,
foregroundApps: new Map(current.foregroundApps).set(userId, meta),
}));
};
const unlisteners = await Promise.all([
events.cursorPositionChanged.listen((event) => {
liveMetadata.update((current) => ({
...current,
cursorPositions: event.payload.positions,
}));
}),
events.foregroundAppChanged.listen((event) => {
if (localId) {
updateForegroundApp(localId, event.payload.meta);
} else {
pendingLocalForegroundApp = event.payload.meta;
}
}),
events.friendForegroundAppChanged.listen((event) => {
updateForegroundApp(event.payload.friendId, event.payload.meta);
}),
]);
try {
localId = await commands.getPublicKey();
liveMetadata.update((current) => ({ ...current, localId }));
if (pendingLocalForegroundApp) {
updateForegroundApp(localId, pendingLocalForegroundApp);
pendingLocalForegroundApp = null;
}
} catch (error) {
liveMetadataListenerError.set(String(error));
}
return () => unlisteners.forEach((unlisten) => unlisten());
}
+19
View File
@@ -0,0 +1,19 @@
import { writable } from "svelte/store";
import { commands, events, type User } from "$lib/bindings";
export const profile = writable<User | null>(null);
export const profileListenerError = writable("");
export async function initProfileListener() {
const unlisten = await events.profileChanged.listen((event) => {
profile.set(event.payload.profile);
});
try {
profile.set(await commands.getProfile());
} catch (error) {
profileListenerError.set(String(error));
}
return unlisten;
}
+19
View File
@@ -0,0 +1,19 @@
import { writable } from "svelte/store";
import { commands, events, type Remote } from "$lib/bindings";
export const remotes = writable<Remote[]>([]);
export const remotesListenerError = writable("");
export async function initRemotesListener() {
const unlisten = await events.remotesChanged.listen((event) => {
remotes.set(event.payload.remotes);
});
try {
remotes.set(await commands.listRemotes());
} catch (error) {
remotesListenerError.set(String(error));
}
return unlisten;
}
+5
View File
@@ -1,8 +1,13 @@
<script>
import { browser } from "$app/environment";
import { onMount } from "svelte";
import { initAppListeners } from "$lib/listeners";
import "../app.css";
let { children } = $props();
onMount(initAppListeners);
if (browser) {
document.addEventListener("contextmenu", (e) => {
e.preventDefault();