Skip to content

Commit 57b8c32

Browse files
Refactor UI views and import i18n
Co-authored-by: GZTime <[email protected]>
1 parent 1ff820f commit 57b8c32

File tree

18 files changed

+314
-328
lines changed

18 files changed

+314
-328
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ and find the binary in `target/release`.
6565

6666
## Further Reading
6767

68-
If you want to intergrate `wsrx` in your own server project, you can read the [crate docs](https://docs.rs/crate/wsrx/latest).
68+
If you want to integrate `wsrx` in your own server project, you can read the [crate docs](https://docs.rs/crate/wsrx/latest).
6969

7070
Also, `wsrx` is a simple tool that using plain WebSocket protocol to tunnel TCP connections,
7171
so you can implement your own server / client in other languages you like.

crates/wsrx-desktop-gpui/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ toml = { workspace = true }
4747
# Error handling and utilities
4848
anyhow = "1.0"
4949
chrono = { workspace = true }
50+
phf = { version = "0.13", features = ["macros"] }
5051
thiserror = { workspace = true }
5152
url = { workspace = true }
5253

@@ -66,9 +67,9 @@ tower-http = { workspace = true }
6667
[build-dependencies]
6768
build-target = { workspace = true }
6869
git-version = { workspace = true }
70+
rust-i18n = "3"
6971
rustc_version = { workspace = true }
7072
winres = { workspace = true }
71-
rust-i18n = "3"
7273

7374
[[bin]]
7475
name = "wsrx-desktop-gpui"
@@ -85,5 +86,5 @@ short_description = "Controlled TCP-over-WebSocket forwarding tunnel (GPUI Editi
8586
icon = [
8687
"../../arts/logo.png",
8788
"../../macos/WebSocketReflectorX.icns",
88-
"../../windows/WebSocketReflectorX.ico",
89+
"../../windows/WebSocketReflectorX.ico"
8990
]

crates/wsrx-desktop-gpui/src/components/checkbox.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl IntoElement for Checkbox {
9696
box_div = box_div.child(
9797
// Checkmark icon
9898
svg()
99-
.path("icons/checkmark.svg")
99+
.path("checkmark")
100100
.size(sizes::icon_xs())
101101
.text_color(colors::window_bg()),
102102
);

crates/wsrx-desktop-gpui/src/components/select.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,9 @@ where
123123

124124
if let (Some(index), Some(callback)) =
125125
(this.selected_index, &this.on_select)
126+
&& let Some(item) = this.options.get(index).cloned()
126127
{
127-
if let Some(item) = this.options.get(index).cloned() {
128-
callback(window, cx, item);
129-
}
128+
callback(window, cx, item);
130129
}
131130

132131
cx.notify();

crates/wsrx-desktop-gpui/src/components/title_bar.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl TitleBar {
2323

2424
impl Render for TitleBar {
2525
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26-
let window = self.window.clone();
26+
let window = self.window;
2727
let is_macos = cfg!(target_os = "macos");
2828

2929
div()
@@ -39,7 +39,6 @@ impl Render for TitleBar {
3939
.bg(gpui::transparent_black())
4040
// Drag area
4141
.on_mouse_down(MouseButton::Left, {
42-
let window = window.clone();
4342
cx.listener(move |_this, _event: &MouseDownEvent, _window, cx| {
4443
window
4544
.update(cx, |_view, window, _cx| {
@@ -72,7 +71,7 @@ impl Render for TitleBar {
7271
}))
7372
.child(
7473
svg()
75-
.path("icons/navigation.svg")
74+
.path("navigation")
7675
.size(styles::sizes::icon_sm())
7776
.text_color(styles::colors::window_fg()),
7877
),

crates/wsrx-desktop-gpui/src/components/window_controls.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl WindowControls {
1515

1616
impl Render for WindowControls {
1717
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
18-
let window = self.window.clone();
18+
let window = self.window;
1919
let is_macos = cfg!(target_os = "macos");
2020

2121
div()
@@ -36,7 +36,6 @@ impl Render for WindowControls {
3636
.hover(|this| this.bg(styles::colors::layer_2()))
3737
.cursor_pointer()
3838
.on_click({
39-
let window = window.clone();
4039
cx.listener(move |_this, _event, _window, cx| {
4140
window
4241
.update(cx, |_view, window, _cx| {
@@ -47,7 +46,7 @@ impl Render for WindowControls {
4746
})
4847
.child(
4948
svg()
50-
.path("icons/subtract.svg")
49+
.path("subtract")
5150
.size(styles::sizes::icon_sm())
5251
.text_color(styles::colors::window_fg()),
5352
),
@@ -64,7 +63,6 @@ impl Render for WindowControls {
6463
.hover(|this| this.bg(styles::colors::layer_2()))
6564
.cursor_pointer()
6665
.on_click({
67-
let window = window.clone();
6866
cx.listener(move |_this, _event, _window, cx| {
6967
window
7068
.update(cx, |_view, window, _cx| {
@@ -75,7 +73,7 @@ impl Render for WindowControls {
7573
})
7674
.child(
7775
svg()
78-
.path("icons/maximize.svg")
76+
.path("maximize")
7977
.size(styles::sizes::icon_sm())
8078
.text_color(styles::colors::window_fg()),
8179
),
@@ -96,7 +94,7 @@ impl Render for WindowControls {
9694
}))
9795
.child(
9896
svg()
99-
.path("icons/dismiss.svg")
97+
.path("dismiss")
10098
.size(styles::sizes::icon_sm())
10199
.text_color(styles::colors::window_fg()),
102100
),
Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Embedded SVG icons
22
// All SVG files are embedded directly into the binary at compile time
33

4+
use gpui::SharedString;
5+
46
pub const HOME: &str = include_str!("../icons/home.svg");
57
pub const CODE: &str = include_str!("../icons/code.svg");
68
pub const SETTINGS: &str = include_str!("../icons/settings.svg");
@@ -18,25 +20,33 @@ pub const CHECKBOX_UNCHECKED: &str = include_str!("../icons/checkbox-unchecked.s
1820
pub const ARROW_UP_RIGHT: &str = include_str!("../icons/arrow-up-right.svg");
1921
pub const ARROW_SYNC_OFF: &str = include_str!("../icons/arrow-sync-off.svg");
2022

23+
static ICON_MAP: phf::Map<&'static str, &'static str> = phf::phf_map! {
24+
"home" => HOME,
25+
"code" => CODE,
26+
"settings" => SETTINGS,
27+
"globe-star" => GLOBE_STAR,
28+
"navigation" => NAVIGATION,
29+
"logo" => LOGO,
30+
"logo-stroked" => LOGO_STROKED,
31+
"warning" => WARNING,
32+
"dismiss" => DISMISS,
33+
"maximize" => MAXIMIZE,
34+
"subtract" => SUBTRACT,
35+
"lock-closed" => LOCK_CLOSED,
36+
"checkmark" => CHECKMARK,
37+
"checkbox-unchecked" => CHECKBOX_UNCHECKED,
38+
"arrow-up-right" => ARROW_UP_RIGHT,
39+
"arrow-sync-off" => ARROW_SYNC_OFF,
40+
};
41+
2142
/// Get icon SVG content by name
2243
pub fn get_icon(name: &str) -> Option<&'static str> {
23-
match name {
24-
"home" => Some(HOME),
25-
"code" => Some(CODE),
26-
"settings" => Some(SETTINGS),
27-
"globe-star" => Some(GLOBE_STAR),
28-
"navigation" => Some(NAVIGATION),
29-
"logo" => Some(LOGO),
30-
"logo-stroked" => Some(LOGO_STROKED),
31-
"warning" => Some(WARNING),
32-
"dismiss" => Some(DISMISS),
33-
"maximize" => Some(MAXIMIZE),
34-
"subtract" => Some(SUBTRACT),
35-
"lock-closed" => Some(LOCK_CLOSED),
36-
"checkmark" => Some(CHECKMARK),
37-
"checkbox-unchecked" => Some(CHECKBOX_UNCHECKED),
38-
"arrow-up-right" => Some(ARROW_UP_RIGHT),
39-
"arrow-sync-off" => Some(ARROW_SYNC_OFF),
40-
_ => None,
41-
}
44+
ICON_MAP.get(name).copied()
45+
}
46+
47+
pub fn list_icons() -> Vec<SharedString> {
48+
ICON_MAP
49+
.keys()
50+
.map(|k| SharedString::new_static(k))
51+
.collect()
4252
}

crates/wsrx-desktop-gpui/src/logging.rs

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,112 @@
1-
// Logging setup for wsrx-desktop-gpui
2-
use std::fs;
1+
// UI Logger - Custom tracing subscriber that sends logs to the UI
2+
//
3+
// This layer captures tracing events and forwards them to a channel
4+
// that the UI can consume to display logs in real-time.
35

4-
use anyhow::Result;
5-
use directories::ProjectDirs;
6-
use tracing_appender::non_blocking;
7-
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
6+
use std::sync::Arc;
87

9-
pub fn setup() -> Result<(
8+
use chrono::Local;
9+
use tokio::sync::mpsc;
10+
use tracing::{Event, Level, Subscriber};
11+
use tracing_subscriber::{Layer, layer::Context};
12+
13+
use crate::models::LogEntry;
14+
15+
/// A tracing layer that sends log entries to the UI via a channel
16+
pub struct UILogLayer {
17+
sender: Arc<mpsc::UnboundedSender<LogEntry>>,
18+
}
19+
20+
impl UILogLayer {
21+
/// Create a new UI log layer
22+
/// Returns the layer and a receiver for consuming log entries
23+
pub fn new() -> (Self, mpsc::UnboundedReceiver<LogEntry>) {
24+
let (sender, receiver) = mpsc::unbounded_channel();
25+
let layer = Self {
26+
sender: Arc::new(sender),
27+
};
28+
(layer, receiver)
29+
}
30+
}
31+
32+
impl<S> Layer<S> for UILogLayer
33+
where
34+
S: Subscriber,
35+
{
36+
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
37+
// Extract log level
38+
let level = match *event.metadata().level() {
39+
Level::TRACE => "TRACE",
40+
Level::DEBUG => "DEBUG",
41+
Level::INFO => "INFO",
42+
Level::WARN => "WARN",
43+
Level::ERROR => "ERROR",
44+
};
45+
46+
// Extract target (module path)
47+
let target = event.metadata().target();
48+
49+
// Format timestamp
50+
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
51+
52+
// Extract message from event
53+
// Note: This is a simplified approach. For production, you'd want to
54+
// use a visitor pattern to properly extract all fields.
55+
let mut message = String::new();
56+
event.record(&mut MessageVisitor {
57+
message: &mut message,
58+
});
59+
60+
// Create log entry
61+
let log_entry = LogEntry {
62+
timestamp,
63+
level: level.to_string(),
64+
target: target.to_string(),
65+
message,
66+
};
67+
68+
// Send to UI (ignore errors if receiver is dropped)
69+
let _ = self.sender.send(log_entry);
70+
}
71+
}
72+
73+
/// Visitor for extracting the message from a tracing event
74+
struct MessageVisitor<'a> {
75+
message: &'a mut String,
76+
}
77+
78+
impl<'a> tracing::field::Visit for MessageVisitor<'a> {
79+
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
80+
if field.name() == "message" {
81+
*self.message = format!("{:?}", value);
82+
// Remove quotes added by Debug formatting
83+
if self.message.starts_with('"') && self.message.ends_with('"') {
84+
*self.message = self.message[1..self.message.len() - 1].to_string();
85+
}
86+
} else {
87+
// Append other fields to the message
88+
if !self.message.is_empty() {
89+
self.message.push_str(", ");
90+
}
91+
self.message
92+
.push_str(&format!("{}={:?}", field.name(), value));
93+
}
94+
}
95+
}
96+
97+
/// Set up logging with UI layer
98+
/// Returns guards for file and console loggers, plus a receiver for UI logs
99+
pub fn setup_with_ui() -> anyhow::Result<(
10100
tracing_appender::non_blocking::WorkerGuard,
11101
tracing_appender::non_blocking::WorkerGuard,
102+
mpsc::UnboundedReceiver<LogEntry>,
12103
)> {
104+
use std::fs;
105+
106+
use directories::ProjectDirs;
107+
use tracing_appender::non_blocking;
108+
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
109+
13110
// Get platform-specific directories
14111
let proj_dirs = ProjectDirs::from("org", "xdsec", "wsrx-desktop-gpui")
15112
.ok_or_else(|| anyhow::anyhow!("Failed to get project directories"))?;
@@ -24,7 +121,10 @@ pub fn setup() -> Result<(
24121
let file_appender = tracing_appender::rolling::daily(log_dir, "wsrx-desktop-gpui.log");
25122
let (file_non_blocking, file_guard) = non_blocking(file_appender);
26123

27-
// Set up the subscriber with both console and file output
124+
// UI logger
125+
let (ui_layer, ui_receiver) = UILogLayer::new();
126+
127+
// Set up the subscriber with console, file, and UI output
28128
tracing_subscriber::registry()
29129
.with(
30130
fmt::layer()
@@ -37,9 +137,10 @@ pub fn setup() -> Result<(
37137
.with_writer(file_non_blocking)
38138
.with_filter(EnvFilter::from_default_env()),
39139
)
140+
.with(ui_layer)
40141
.init();
41142

42-
tracing::info!("Logging initialized for wsrx-desktop-gpui");
143+
tracing::info!("Logger initialized.");
43144

44-
Ok((console_guard, file_guard))
145+
Ok((console_guard, file_guard, ui_receiver))
45146
}

0 commit comments

Comments
 (0)