From 7088f6850718f4f9f43902b6713b7d2dec59c662 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Thu, 27 Aug 2026 00:09:20 +0800 Subject: [PATCH] control panel ui --- src-tauri/capabilities/default.json | 4 +- src-tauri/src/lib.rs | 1 + src-tauri/src/ui/control_panel/mod.rs | 49 +++++ src-tauri/src/ui/mod.rs | 2 +- src/lib/bindings.ts | 3 + .../control-panel/activity-panel.svelte | 112 ++++++++++ .../control-panel/friends-panel.svelte | 160 ++++++++++++++ .../control-panel/general-panel.svelte | 119 +++++++++++ .../control-panel/network-panel.svelte | 198 ++++++++++++++++++ .../control-panel/panel-message.svelte | 19 ++ src/lib/components/control-panel/types.ts | 15 ++ src/lib/icons/info.svelte | 15 ++ src/routes/control-panel/+page.svelte | 132 +++++++++++- .../control-panel/add/friend/+page.svelte | 96 +++++++++ .../control-panel/add/network/+page.svelte | 121 +++++++++++ .../edit/network/[id]/+page.svelte | 172 +++++++++++++++ 16 files changed, 1211 insertions(+), 7 deletions(-) create mode 100644 src/lib/components/control-panel/activity-panel.svelte create mode 100644 src/lib/components/control-panel/friends-panel.svelte create mode 100644 src/lib/components/control-panel/general-panel.svelte create mode 100644 src/lib/components/control-panel/network-panel.svelte create mode 100644 src/lib/components/control-panel/panel-message.svelte create mode 100644 src/lib/components/control-panel/types.ts create mode 100644 src/lib/icons/info.svelte create mode 100644 src/routes/control-panel/add/friend/+page.svelte create mode 100644 src/routes/control-panel/add/network/+page.svelte create mode 100644 src/routes/control-panel/edit/network/[id]/+page.svelte diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 250a38d..e365b5a 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,9 +2,11 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for Wyd UI windows", - "windows": ["debug", "scene"], + "windows": ["control-panel", "control-panel-action-*", "debug", "scene"], "permissions": [ "core:default", + "core:window:allow-close", + "core:window:allow-hide", "opener:default" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5827795..1d8b641 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -75,6 +75,7 @@ fn specta_builder() -> tauri_specta::Builder { images::pick_and_send_image, images::send_image_bytes, interactions::send_interaction, + ui::control_panel::open_action_window, ui::scene::update_scene_hitboxes, ]) .events(tauri_specta::collect_events![ diff --git a/src-tauri/src/ui/control_panel/mod.rs b/src-tauri/src/ui/control_panel/mod.rs index 6cac165..6cfdb9e 100644 --- a/src-tauri/src/ui/control_panel/mod.rs +++ b/src-tauri/src/ui/control_panel/mod.rs @@ -1,6 +1,7 @@ use tauri::{AppHandle, Manager, WebviewUrl}; const WINDOW_LABEL: &str = "control-panel"; +const ACTION_WINDOW_PREFIX: &str = "control-panel-action-"; pub fn init(handle: &AppHandle) { if let Some(window) = handle.get_webview_window(WINDOW_LABEL) { @@ -33,3 +34,51 @@ pub fn init(handle: &AppHandle) { } }); } + +#[tauri::command] +#[specta::specta] +pub fn open_action_window( + handle: AppHandle, + name: String, + title: String, + page_url: String, +) -> Result<(), String> { + if name.is_empty() + || !name + .chars() + .all(|character| character.is_ascii_alphanumeric() || character == '-') + { + return Err("Action window name must contain only letters, numbers, or hyphens.".into()); + } + if title.trim().is_empty() || title.chars().count() > 80 { + return Err("Action window title must contain between 1 and 80 characters.".into()); + } + if !(page_url.starts_with("/control-panel/add/") + || page_url.starts_with("/control-panel/edit/")) + || page_url.contains("..") + || page_url.contains(['?', '#']) + { + return Err("Action windows can only open control-panel add or edit pages.".into()); + } + + let label = format!("{ACTION_WINDOW_PREFIX}{name}"); + if let Some(window) = handle.get_webview_window(&label) { + window.show().map_err(|error| error.to_string())?; + window.center().map_err(|error| error.to_string())?; + window.set_focus().map_err(|error| error.to_string())?; + return Ok(()); + } + + tauri::WebviewWindowBuilder::new(&handle, label, WebviewUrl::App(page_url.into())) + .title(title) + .inner_size(300.0, 450.0) + .min_inner_size(300.0, 450.0) + .resizable(false) + .minimizable(false) + .maximizable(false) + .skip_taskbar(true) + .center() + .build() + .map(|_| ()) + .map_err(|error| error.to_string()) +} diff --git a/src-tauri/src/ui/mod.rs b/src-tauri/src/ui/mod.rs index 7605e2c..7c5c18f 100644 --- a/src-tauri/src/ui/mod.rs +++ b/src-tauri/src/ui/mod.rs @@ -1,6 +1,6 @@ use tauri::AppHandle; -mod control_panel; +pub mod control_panel; #[cfg(debug_assertions)] mod debug; pub mod scene; diff --git a/src/lib/bindings.ts b/src/lib/bindings.ts index ecdb713..ccb4b6f 100644 --- a/src/lib/bindings.ts +++ b/src/lib/bindings.ts @@ -56,6 +56,9 @@ async sendImageBytes(recipientId: string, bytes: number[]) : Promise { async sendInteraction(recipientId: string, content: InteractionContent) : Promise { return await TAURI_INVOKE("send_interaction", { recipientId, content }); }, +async openActionWindow(name: string, title: string, pageUrl: string) : Promise { + return await TAURI_INVOKE("open_action_window", { name, title, pageUrl }); +}, async updateSceneHitboxes(hitboxes: SceneHitbox[]) : Promise { return await TAURI_INVOKE("update_scene_hitboxes", { hitboxes }); } diff --git a/src/lib/components/control-panel/activity-panel.svelte b/src/lib/components/control-panel/activity-panel.svelte new file mode 100644 index 0000000..f1ba523 --- /dev/null +++ b/src/lib/components/control-panel/activity-panel.svelte @@ -0,0 +1,112 @@ + + +
+ {#if $liveMetadataListenerError} + {$liveMetadataListenerError} + {:else if userIds().length === 0} + Waiting for activity data. This usually appears after Wyd has started + monitoring. + {/if} + +
+ {#each userIds() as userId (userId)} + {@const cursor = $liveMetadata.cursorPositions[userId]} + {@const app = $liveMetadata.foregroundApps.get(userId)} +
+
+ {#if app?.ico} + + {:else} + + {/if} +
+
+ {nameFor(userId)} + {#if userId === $liveMetadata.localId}Local{/if} +
+

+ {compactId(userId)} +

+
+
+ +
+
Foreground app
+
+ {app?.local ?? app?.unlocal ?? "Not available"} +
+
Cursor
+
+ {cursor + ? `${Math.round(cursor.raw.x)}, ${Math.round(cursor.raw.y)} px` + : "Not available"} +
+
Mapped
+
+ {cursor + ? `${cursor.mapped.x.toFixed(3)}, ${cursor.mapped.y.toFixed(3)}` + : "Not available"} +
+
+
+ {/each} +
+
diff --git a/src/lib/components/control-panel/friends-panel.svelte b/src/lib/components/control-panel/friends-panel.svelte new file mode 100644 index 0000000..998683c --- /dev/null +++ b/src/lib/components/control-panel/friends-panel.svelte @@ -0,0 +1,160 @@ + + +
+ {#if error || $friendsListenerError || $friendStatusesListenerError} + {error || + $friendsListenerError || + $friendStatusesListenerError} + {/if} + + {#if mode === "remove" && selected} + + Applying will remove {selected.displayName}. They will + immediately go offline and stop receiving your activity. + + + {:else} +
+
+ NameStatus +
+
+ {#if $friends.length === 0} +
+

No friends yet

+

+ Add someone using their public key. +

+
+ {:else} + {#each $friends as friend (friend.id)} + {@const online = $onlineFriendIds.has(friend.id)} + + {/each} + {/if} +
+
+ +
+ + +
+ {/if} +
diff --git a/src/lib/components/control-panel/general-panel.svelte b/src/lib/components/control-panel/general-panel.svelte new file mode 100644 index 0000000..c5f2ba6 --- /dev/null +++ b/src/lib/components/control-panel/general-panel.svelte @@ -0,0 +1,119 @@ + + +
+ {#if error || $profileListenerError} + {error || $profileListenerError} + {/if} + +
+ Identity + + + +

+ This name can be changed at any time. +

+ + + {#if $profile} +
+ + +
+ {:else if $profileListenerError} + + {:else} +
+ {/if} +
+
diff --git a/src/lib/components/control-panel/network-panel.svelte b/src/lib/components/control-panel/network-panel.svelte new file mode 100644 index 0000000..03e7975 --- /dev/null +++ b/src/lib/components/control-panel/network-panel.svelte @@ -0,0 +1,198 @@ + + +
+ {#if error || $remotesListenerError || $connectionStatusesListenerError} + {error || + $remotesListenerError || + $connectionStatusesListenerError} + {/if} + + {#if mode === "remove" && selected} + + Applying will remove {selected.name ?? selected.address}. + Existing connections through this server may stop. + + + {:else} +
+
+ ServerConnection +
+
+ {#if $remotes.length === 0} +
+

No servers configured

+

+ Add a server to connect with friends. +

+
+ {:else} + {#each $remotes as remote (remote.id)} + {@const state = statusFor(remote.id)} + + {/each} + {/if} +
+
+ +
+ +
+ + +
+
+ {/if} +
diff --git a/src/lib/components/control-panel/panel-message.svelte b/src/lib/components/control-panel/panel-message.svelte new file mode 100644 index 0000000..34fcf5e --- /dev/null +++ b/src/lib/components/control-panel/panel-message.svelte @@ -0,0 +1,19 @@ + + +
+ {@render children()} +
diff --git a/src/lib/components/control-panel/types.ts b/src/lib/components/control-panel/types.ts new file mode 100644 index 0000000..1bb517d --- /dev/null +++ b/src/lib/components/control-panel/types.ts @@ -0,0 +1,15 @@ +export type PanelState = { + dirty: boolean; + busy: boolean; +}; + +export type PanelActions = { + apply: () => Promise; + reset: () => void; +}; + +export type RegisterPanel = ( + name: string, + actions: PanelActions, + state: PanelState, +) => void; diff --git a/src/lib/icons/info.svelte b/src/lib/icons/info.svelte new file mode 100644 index 0000000..39fc706 --- /dev/null +++ b/src/lib/icons/info.svelte @@ -0,0 +1,15 @@ + diff --git a/src/routes/control-panel/+page.svelte b/src/routes/control-panel/+page.svelte index 6ffa2d9..48c157c 100644 --- a/src/routes/control-panel/+page.svelte +++ b/src/routes/control-panel/+page.svelte @@ -1,9 +1,131 @@ -
-
-

Control panel

+Wyd Properties + +
+
+ {#each tabs as tab} + +
+
+ {#if tab.id === "general"} + + {:else if tab.id === "friends"} + + {:else if tab.id === "network"} + + {:else} + + {/if} +
+
+ {/each}
-
+ +
+ + + +
+ diff --git a/src/routes/control-panel/add/friend/+page.svelte b/src/routes/control-panel/add/friend/+page.svelte new file mode 100644 index 0000000..a41e932 --- /dev/null +++ b/src/routes/control-panel/add/friend/+page.svelte @@ -0,0 +1,96 @@ + + +Add Friend + event.key === "Escape" && !busy && void closeWindow()} +/> + +
+
+
+

Add a friend

+

+ Enter the identity your friend shared with you. +

+
+ + {#if error}{error}{/if} + +
+ Friend identity + + + + +
+
+ +
+ + +
+
diff --git a/src/routes/control-panel/add/network/+page.svelte b/src/routes/control-panel/add/network/+page.svelte new file mode 100644 index 0000000..c37fef8 --- /dev/null +++ b/src/routes/control-panel/add/network/+page.svelte @@ -0,0 +1,121 @@ + + +Add Server + event.key === "Escape" && !busy && void closeWindow()} +/> + +
+
+
+

Add a server

+

+ Configure a remote server used to connect with friends. +

+
+ + {#if error}{error}{/if} + +
+ Server details + + + + + + +

Leave port blank to use the server default.

+
+
+ +
+ + +
+
diff --git a/src/routes/control-panel/edit/network/[id]/+page.svelte b/src/routes/control-panel/edit/network/[id]/+page.svelte new file mode 100644 index 0000000..e022760 --- /dev/null +++ b/src/routes/control-panel/edit/network/[id]/+page.svelte @@ -0,0 +1,172 @@ + + +Edit Server + event.key === "Escape" && !busy && void closeWindow()} +/> + +
+
+
+

Edit server

+

+ Update how Wyd connects to this remote server. +

+
+ + {#if error}{error}{/if} + +
+ Server details + + + + + + +

Leave port blank to use the server default.

+
+
+ +
+ + +
+