Skip to content

Commit c554a57

Browse files
Merge pull request #119 from datum-cloud/fix/make-tray-buttons-responsive
Make tray menu buttons work and add new "add tunnel" button to menu
2 parents 7331410 + bacf1f7 commit c554a57

5 files changed

Lines changed: 161 additions & 56 deletions

File tree

ui/assets/tailwind.css

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
--color-gray-300: oklch(87.2% 0.01 258.338);
1313
--color-gray-500: oklch(55.1% 0.027 264.364);
1414
--color-gray-700: oklch(37.3% 0.034 259.733);
15+
--color-black: #000;
1516
--color-white: #fff;
1617
--spacing: 0.25rem;
1718
--container-xs: 20rem;
18-
--container-sm: 24rem;
1919
--container-md: 28rem;
2020
--container-lg: 32rem;
2121
--container-4xl: 56rem;
@@ -267,6 +267,9 @@
267267
.z-\[60\] {
268268
z-index: 60;
269269
}
270+
.z-\[100\] {
271+
z-index: 100;
272+
}
270273
.container {
271274
width: 100%;
272275
@media (width >= 40rem) {
@@ -324,6 +327,9 @@
324327
.mb-0\.5 {
325328
margin-bottom: calc(var(--spacing) * 0.5);
326329
}
330+
.mb-2 {
331+
margin-bottom: calc(var(--spacing) * 2);
332+
}
327333
.mb-3 {
328334
margin-bottom: calc(var(--spacing) * 3);
329335
}
@@ -336,6 +342,9 @@
336342
.mb-7 {
337343
margin-bottom: calc(var(--spacing) * 7);
338344
}
345+
.ml-2 {
346+
margin-left: calc(var(--spacing) * 2);
347+
}
339348
.ml-3 {
340349
margin-left: calc(var(--spacing) * 3);
341350
}
@@ -348,6 +357,9 @@
348357
.block {
349358
display: block;
350359
}
360+
.contents {
361+
display: contents;
362+
}
351363
.flex {
352364
display: flex;
353365
}
@@ -416,6 +428,9 @@
416428
.h-\[45vh\] {
417429
height: 45vh;
418430
}
431+
.h-\[380px\] {
432+
height: 380px;
433+
}
419434
.h-full {
420435
height: 100%;
421436
}
@@ -488,6 +503,9 @@
488503
.w-\[37px\] {
489504
width: 37px;
490505
}
506+
.w-\[320px\] {
507+
width: 320px;
508+
}
491509
.w-\[452px\] {
492510
width: 452px;
493511
}
@@ -659,6 +677,13 @@
659677
margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)));
660678
}
661679
}
680+
.space-y-3 {
681+
:where(& > :not(:last-child)) {
682+
--tw-space-y-reverse: 0;
683+
margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));
684+
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
685+
}
686+
}
662687
.space-y-4 {
663688
:where(& > :not(:last-child)) {
664689
--tw-space-y-reverse: 0;
@@ -793,6 +818,12 @@
793818
background-color: color-mix(in oklab, var(--background) 50%, transparent);
794819
}
795820
}
821+
.bg-black\/20 {
822+
background-color: color-mix(in srgb, #000 20%, transparent);
823+
@supports (color: color-mix(in lab, red, red)) {
824+
background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
825+
}
826+
}
796827
.bg-button-primary-background\/90 {
797828
background-color: var(--button-primary-background);
798829
@supports (color: color-mix(in lab, red, red)) {
@@ -949,9 +980,18 @@
949980
.py-7 {
950981
padding-block: calc(var(--spacing) * 7);
951982
}
983+
.py-8 {
984+
padding-block: calc(var(--spacing) * 8);
985+
}
952986
.pt-2 {
953987
padding-top: calc(var(--spacing) * 2);
954988
}
989+
.pt-12 {
990+
padding-top: calc(var(--spacing) * 12);
991+
}
992+
.pr-4 {
993+
padding-right: calc(var(--spacing) * 4);
994+
}
955995
.pb-12 {
956996
padding-bottom: calc(var(--spacing) * 12);
957997
}
@@ -1203,6 +1243,9 @@
12031243
.fade-in {
12041244
--tw-enter-opacity: 0;
12051245
}
1246+
.running {
1247+
animation-play-state: running;
1248+
}
12061249
.group-data-\[state\=checked\]\:translate-x-5 {
12071250
&:is(:where(.group)[data-state="checked"] *) {
12081251
--tw-translate-x: calc(var(--spacing) * 5);

ui/src/main.rs

Lines changed: 84 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
use dioxus::prelude::*;
2-
#[cfg(feature = "desktop")]
3-
use n0_error::Result;
42
use std::sync::OnceLock;
53
use tracing::info;
64
use tracing_appender::non_blocking::WorkerGuard;
@@ -9,16 +7,16 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
97
use crate::components::{Head, Splash, UpdateDialog};
108
use crate::state::AppState;
119
use crate::views::{
12-
Chrome, JoinProxy, Login, ProxiesList, SelectProject, Settings, TunnelBandwidth,
10+
Chrome, JoinProxy, Login, ProxiesList, SelectProject, Settings, TrayNavHandler, TunnelBandwidth,
1311
};
1412

1513
#[cfg(feature = "desktop")]
1614
use dioxus_desktop::{
1715
trayicon::{
18-
menu::{Menu, MenuItem, PredefinedMenuItem},
16+
menu::{IconMenuItem, Menu, MenuItem, NativeIcon, PredefinedMenuItem},
1917
Icon, TrayIcon, TrayIconBuilder,
2018
},
21-
use_tray_menu_event_handler, use_window,
19+
use_muda_event_handler, use_window,
2220
};
2321

2422
mod components;
@@ -51,6 +49,7 @@ static MANUAL_UPDATE_CHECK_FLAG: std::sync::atomic::AtomicBool =
5149
#[derive(Debug, Clone, Routable, PartialEq)]
5250
#[rustfmt::skip]
5351
enum Route {
52+
#[layout(TrayNavHandler)]
5453
#[route("/")]
5554
Login{},
5655
#[layout(Chrome)]
@@ -95,9 +94,6 @@ fn main() {
9594
#[cfg(all(feature = "desktop", target_os = "linux"))]
9695
gtk::init().unwrap();
9796

98-
#[cfg(feature = "desktop")]
99-
let _tray_icon = init_menu_bar().unwrap();
100-
10197
#[cfg(feature = "desktop")]
10298
{
10399
use dioxus_desktop::{Config, LogicalSize, WindowBuilder, WindowCloseBehaviour};
@@ -300,36 +296,52 @@ fn App() -> Element {
300296
});
301297
}
302298

299+
let mut open_add_tunnel_from_tray = use_signal(|| false);
300+
let mut login_state_for_tray = use_signal(|| None::<lib::datum_cloud::LoginState>);
301+
302+
// Create tray when app state is ready. Must run before early return.
303303
#[cfg(feature = "desktop")]
304-
use_tray_menu_event_handler(move |event| -> () {
305-
// The event ID corresponds to the menu item text
306-
let _: () = match event.id.0.as_str() {
307-
"About Datum" => {
308-
let _ = open::that("https://datum.net");
309-
()
310-
}
311-
"Show Window" => {
312-
use_window().set_visible(true);
313-
()
314-
}
315-
"Hide" => {
316-
use_window().set_visible(false);
317-
()
318-
}
319-
"Check for Updates..." => {
320-
manual_update_check.set(true);
321-
update_check_in_progress.set(true);
322-
()
323-
}
324-
"Quit" => {
325-
std::process::exit(0);
304+
{
305+
let mut tray_holder = use_signal(|| None::<TrayIcon>);
306+
307+
use_effect(move || {
308+
if !app_state_ready() {
309+
return;
326310
}
327-
_ => {
328-
eprintln!("Unknown menu event: {}", event.id.0);
329-
()
311+
if (*tray_holder.peek()).is_some() {
312+
return;
330313
}
331-
};
332-
});
314+
let tray = init_tray();
315+
tray_holder.set(Some(tray));
316+
});
317+
318+
let window = use_window();
319+
use_muda_event_handler(move |event| -> () {
320+
let _: () = match event.id.0.as_str() {
321+
"about" => {
322+
let _ = open::that("https://datum.net");
323+
}
324+
"show" => {
325+
window.set_visible(true);
326+
window.set_focus();
327+
}
328+
"hide" => {
329+
window.set_visible(false);
330+
}
331+
"new_tunnel" => {
332+
if login_state_for_tray() == Some(lib::datum_cloud::LoginState::Missing)
333+
|| login_state_for_tray().is_none()
334+
{
335+
return;
336+
}
337+
window.set_visible(true);
338+
window.set_focus();
339+
open_add_tunnel_from_tray.set(true);
340+
}
341+
_ => {}
342+
};
343+
});
344+
}
333345

334346
if !app_state_ready() {
335347
return rsx! {
@@ -345,6 +357,11 @@ fn App() -> Element {
345357
provide_context(auth_changed);
346358

347359
let state_for_auth_watch = consume_context::<AppState>();
360+
use_effect(move || {
361+
let _ = auth_changed();
362+
let state = consume_context::<AppState>();
363+
login_state_for_tray.set(Some(state.datum().login_state()));
364+
});
348365
use_future(move || {
349366
let state_for_auth_watch = state_for_auth_watch.clone();
350367
let mut auth_changed = auth_changed;
@@ -365,6 +382,9 @@ fn App() -> Element {
365382
in_progress: update_check_in_progress,
366383
});
367384

385+
// Tray can trigger opening the add tunnel dialog; Chrome (inside Router) handles navigation + dialog.
386+
provide_context(open_add_tunnel_from_tray);
387+
368388
rsx! {
369389
div { class: "theme-alpha",
370390
TitleBar {}
@@ -387,43 +407,52 @@ fn App() -> Element {
387407
}
388408

389409
#[cfg(feature = "desktop")]
390-
fn init_menu_bar() -> Result<TrayIcon> {
391-
// Initialize the tray menu
392-
410+
fn init_tray() -> TrayIcon {
393411
use n0_error::StdResultExt;
412+
394413
let tray_menu = Menu::new();
414+
let about_item = MenuItem::with_id("about", "About Datum", true, None);
415+
let show_item = MenuItem::with_id("show", "Show Window", true, None);
416+
let hide_item = MenuItem::with_id("hide", "Hide", true, None);
417+
418+
#[cfg(target_os = "macos")]
419+
let new_tunnel_item = IconMenuItem::with_id_and_native_icon(
420+
"new_tunnel",
421+
"New Tunnel",
422+
true,
423+
Some(NativeIcon::Add),
424+
None,
425+
);
426+
#[cfg(not(target_os = "macos"))]
427+
let new_tunnel_item = IconMenuItem::with_id("new_tunnel", "New Tunnel", true, None, None);
395428

396-
// Create menu items with IDs for event handling
397-
let about_item = MenuItem::new("About Datum", true, None);
398-
let show_item = MenuItem::new("Show Window", true, None);
399-
let hide_item = MenuItem::new("Hide", true, None);
400-
let separator1 = PredefinedMenuItem::separator();
401-
let check_updates_item = MenuItem::new("Check for Updates...", true, None);
402-
let separator2 = PredefinedMenuItem::separator();
403-
let quit_item = MenuItem::new("Quit", true, None);
429+
let version_item = MenuItem::with_id(
430+
"version",
431+
format!("Version {}", env!("CARGO_PKG_VERSION")),
432+
false,
433+
None,
434+
);
435+
let sep = PredefinedMenuItem::separator();
404436

405-
// Build the menu structure (macOS-style: About, Show, Hide, sep, Check for Updates, sep, Quit)
406437
tray_menu
407438
.append_items(&[
439+
&new_tunnel_item,
440+
&sep,
408441
&about_item,
409442
&show_item,
410443
&hide_item,
411-
&separator1,
412-
&check_updates_item,
413-
&separator2,
414-
&quit_item,
444+
&sep,
445+
&version_item,
415446
])
416447
.expect("Failed to build tray menu");
417448

418-
let icon = icon();
419-
420-
// Build the tray icon
421449
TrayIconBuilder::new()
422450
.with_menu(Box::new(tray_menu))
423451
.with_tooltip("Datum")
424-
.with_icon(icon)
452+
.with_icon(icon())
425453
.build()
426454
.std_context("building tray icon")
455+
.expect("failed to build tray icon")
427456
}
428457

429458
/// Load an icon from a PNG file for the tray

ui/src/views/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod navbar;
1010
mod proxies_list;
1111
mod select_project;
1212
mod settings;
13+
mod tray_nav_handler;
1314
mod tunnel_bandwidth;
1415

1516
pub use join_proxy::JoinProxy;
@@ -18,4 +19,5 @@ pub use navbar::*;
1819
pub use proxies_list::{ProxiesList, TunnelCard};
1920
pub use select_project::SelectProject;
2021
pub use settings::Settings;
22+
pub use tray_nav_handler::TrayNavHandler;
2123
pub use tunnel_bandwidth::TunnelBandwidth;

ui/src/views/navbar.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,21 @@ pub fn Chrome() -> Element {
2929
let mut add_tunnel_dialog_open = use_signal(|| false);
3030
let mut invite_user_dialog_open = use_signal(|| false);
3131
let mut editing_tunnel = use_signal(|| None::<lib::TunnelSummary>);
32+
let mut open_add_tunnel_from_tray = consume_context::<Signal<bool>>();
3233

3334
provide_context(OpenEditTunnelDialog {
3435
editing_tunnel,
3536
dialog_open: add_tunnel_dialog_open,
3637
});
3738

39+
use_effect(move || {
40+
if open_add_tunnel_from_tray() {
41+
nav.push(Route::ProxiesList {});
42+
add_tunnel_dialog_open.set(true);
43+
open_add_tunnel_from_tray.set(false);
44+
}
45+
});
46+
3847
use_effect(move || {
3948
let _ = auth_changed();
4049
if state.datum().login_state() == LoginState::Missing {

0 commit comments

Comments
 (0)