Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion ui/assets/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
--color-gray-300: oklch(87.2% 0.01 258.338);
--color-gray-500: oklch(55.1% 0.027 264.364);
--color-gray-700: oklch(37.3% 0.034 259.733);
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
--container-xs: 20rem;
--container-sm: 24rem;
--container-md: 28rem;
--container-lg: 32rem;
--container-4xl: 56rem;
Expand Down Expand Up @@ -267,6 +267,9 @@
.z-\[60\] {
z-index: 60;
}
.z-\[100\] {
z-index: 100;
}
.container {
width: 100%;
@media (width >= 40rem) {
Expand Down Expand Up @@ -324,6 +327,9 @@
.mb-0\.5 {
margin-bottom: calc(var(--spacing) * 0.5);
}
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
}
.mb-3 {
margin-bottom: calc(var(--spacing) * 3);
}
Expand All @@ -336,6 +342,9 @@
.mb-7 {
margin-bottom: calc(var(--spacing) * 7);
}
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.ml-3 {
margin-left: calc(var(--spacing) * 3);
}
Expand All @@ -348,6 +357,9 @@
.block {
display: block;
}
.contents {
display: contents;
}
.flex {
display: flex;
}
Expand Down Expand Up @@ -416,6 +428,9 @@
.h-\[45vh\] {
height: 45vh;
}
.h-\[380px\] {
height: 380px;
}
.h-full {
height: 100%;
}
Expand Down Expand Up @@ -488,6 +503,9 @@
.w-\[37px\] {
width: 37px;
}
.w-\[320px\] {
width: 320px;
}
.w-\[452px\] {
width: 452px;
}
Expand Down Expand Up @@ -659,6 +677,13 @@
margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-3 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-4 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
Expand Down Expand Up @@ -793,6 +818,12 @@
background-color: color-mix(in oklab, var(--background) 50%, transparent);
}
}
.bg-black\/20 {
background-color: color-mix(in srgb, #000 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
}
}
.bg-button-primary-background\/90 {
background-color: var(--button-primary-background);
@supports (color: color-mix(in lab, red, red)) {
Expand Down Expand Up @@ -949,9 +980,18 @@
.py-7 {
padding-block: calc(var(--spacing) * 7);
}
.py-8 {
padding-block: calc(var(--spacing) * 8);
}
.pt-2 {
padding-top: calc(var(--spacing) * 2);
}
.pt-12 {
padding-top: calc(var(--spacing) * 12);
}
.pr-4 {
padding-right: calc(var(--spacing) * 4);
}
.pb-12 {
padding-bottom: calc(var(--spacing) * 12);
}
Expand Down Expand Up @@ -1203,6 +1243,9 @@
.fade-in {
--tw-enter-opacity: 0;
}
.running {
animation-play-state: running;
}
.group-data-\[state\=checked\]\:translate-x-5 {
&:is(:where(.group)[data-state="checked"] *) {
--tw-translate-x: calc(var(--spacing) * 5);
Expand Down
139 changes: 84 additions & 55 deletions ui/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use dioxus::prelude::*;
#[cfg(feature = "desktop")]
use n0_error::Result;
use std::sync::OnceLock;
use tracing::info;
use tracing_appender::non_blocking::WorkerGuard;
Expand All @@ -9,16 +7,16 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use crate::components::{Head, Splash, UpdateDialog};
use crate::state::AppState;
use crate::views::{
Chrome, JoinProxy, Login, ProxiesList, SelectProject, Settings, TunnelBandwidth,
Chrome, JoinProxy, Login, ProxiesList, SelectProject, Settings, TrayNavHandler, TunnelBandwidth,
};

#[cfg(feature = "desktop")]
use dioxus_desktop::{
trayicon::{
menu::{Menu, MenuItem, PredefinedMenuItem},
menu::{IconMenuItem, Menu, MenuItem, NativeIcon, PredefinedMenuItem},
Icon, TrayIcon, TrayIconBuilder,
},
use_tray_menu_event_handler, use_window,
use_muda_event_handler, use_window,
};

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

#[cfg(feature = "desktop")]
let _tray_icon = init_menu_bar().unwrap();

#[cfg(feature = "desktop")]
{
use dioxus_desktop::{Config, LogicalSize, WindowBuilder, WindowCloseBehaviour};
Expand Down Expand Up @@ -300,36 +296,52 @@ fn App() -> Element {
});
}

let mut open_add_tunnel_from_tray = use_signal(|| false);
let mut login_state_for_tray = use_signal(|| None::<lib::datum_cloud::LoginState>);

// Create tray when app state is ready. Must run before early return.
#[cfg(feature = "desktop")]
use_tray_menu_event_handler(move |event| -> () {
// The event ID corresponds to the menu item text
let _: () = match event.id.0.as_str() {
"About Datum" => {
let _ = open::that("https://datum.net");
()
}
"Show Window" => {
use_window().set_visible(true);
()
}
"Hide" => {
use_window().set_visible(false);
()
}
"Check for Updates..." => {
manual_update_check.set(true);
update_check_in_progress.set(true);
()
}
"Quit" => {
std::process::exit(0);
{
let mut tray_holder = use_signal(|| None::<TrayIcon>);

use_effect(move || {
if !app_state_ready() {
return;
}
_ => {
eprintln!("Unknown menu event: {}", event.id.0);
()
if (*tray_holder.peek()).is_some() {
return;
}
};
});
let tray = init_tray();
tray_holder.set(Some(tray));
});

let window = use_window();
use_muda_event_handler(move |event| -> () {
let _: () = match event.id.0.as_str() {
"about" => {
let _ = open::that("https://datum.net");
}
"show" => {
window.set_visible(true);
window.set_focus();
}
"hide" => {
window.set_visible(false);
}
"new_tunnel" => {
if login_state_for_tray() == Some(lib::datum_cloud::LoginState::Missing)
|| login_state_for_tray().is_none()
{
return;
}
window.set_visible(true);
window.set_focus();
open_add_tunnel_from_tray.set(true);
}
_ => {}
};
});
}

if !app_state_ready() {
return rsx! {
Expand All @@ -345,6 +357,11 @@ fn App() -> Element {
provide_context(auth_changed);

let state_for_auth_watch = consume_context::<AppState>();
use_effect(move || {
let _ = auth_changed();
let state = consume_context::<AppState>();
login_state_for_tray.set(Some(state.datum().login_state()));
});
use_future(move || {
let state_for_auth_watch = state_for_auth_watch.clone();
let mut auth_changed = auth_changed;
Expand All @@ -365,6 +382,9 @@ fn App() -> Element {
in_progress: update_check_in_progress,
});

// Tray can trigger opening the add tunnel dialog; Chrome (inside Router) handles navigation + dialog.
provide_context(open_add_tunnel_from_tray);

rsx! {
div { class: "theme-alpha",
TitleBar {}
Expand All @@ -387,43 +407,52 @@ fn App() -> Element {
}

#[cfg(feature = "desktop")]
fn init_menu_bar() -> Result<TrayIcon> {
// Initialize the tray menu

fn init_tray() -> TrayIcon {
use n0_error::StdResultExt;

let tray_menu = Menu::new();
let about_item = MenuItem::with_id("about", "About Datum", true, None);
let show_item = MenuItem::with_id("show", "Show Window", true, None);
let hide_item = MenuItem::with_id("hide", "Hide", true, None);

#[cfg(target_os = "macos")]
let new_tunnel_item = IconMenuItem::with_id_and_native_icon(
"new_tunnel",
"New Tunnel",
true,
Some(NativeIcon::Add),
None,
);
#[cfg(not(target_os = "macos"))]
let new_tunnel_item = IconMenuItem::with_id("new_tunnel", "New Tunnel", true, None, None);

// Create menu items with IDs for event handling
let about_item = MenuItem::new("About Datum", true, None);
let show_item = MenuItem::new("Show Window", true, None);
let hide_item = MenuItem::new("Hide", true, None);
let separator1 = PredefinedMenuItem::separator();
let check_updates_item = MenuItem::new("Check for Updates...", true, None);
let separator2 = PredefinedMenuItem::separator();
let quit_item = MenuItem::new("Quit", true, None);
let version_item = MenuItem::with_id(
"version",
format!("Version {}", env!("CARGO_PKG_VERSION")),
false,
None,
);
let sep = PredefinedMenuItem::separator();

// Build the menu structure (macOS-style: About, Show, Hide, sep, Check for Updates, sep, Quit)
tray_menu
.append_items(&[
&new_tunnel_item,
&sep,
&about_item,
&show_item,
&hide_item,
&separator1,
&check_updates_item,
&separator2,
&quit_item,
&sep,
&version_item,
])
.expect("Failed to build tray menu");

let icon = icon();

// Build the tray icon
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu))
.with_tooltip("Datum")
.with_icon(icon)
.with_icon(icon())
.build()
.std_context("building tray icon")
.expect("failed to build tray icon")
}

/// Load an icon from a PNG file for the tray
Expand Down
2 changes: 2 additions & 0 deletions ui/src/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod navbar;
mod proxies_list;
mod select_project;
mod settings;
mod tray_nav_handler;
mod tunnel_bandwidth;

pub use join_proxy::JoinProxy;
Expand All @@ -18,4 +19,5 @@ pub use navbar::*;
pub use proxies_list::{ProxiesList, TunnelCard};
pub use select_project::SelectProject;
pub use settings::Settings;
pub use tray_nav_handler::TrayNavHandler;
pub use tunnel_bandwidth::TunnelBandwidth;
9 changes: 9 additions & 0 deletions ui/src/views/navbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,21 @@ pub fn Chrome() -> Element {
let mut add_tunnel_dialog_open = use_signal(|| false);
let mut invite_user_dialog_open = use_signal(|| false);
let mut editing_tunnel = use_signal(|| None::<lib::TunnelSummary>);
let mut open_add_tunnel_from_tray = consume_context::<Signal<bool>>();

provide_context(OpenEditTunnelDialog {
editing_tunnel,
dialog_open: add_tunnel_dialog_open,
});

use_effect(move || {
if open_add_tunnel_from_tray() {
nav.push(Route::ProxiesList {});
add_tunnel_dialog_open.set(true);
open_add_tunnel_from_tray.set(false);
}
});

use_effect(move || {
let _ = auth_changed();
if state.datum().login_state() == LoginState::Missing {
Expand Down
Loading
Loading