Skip to content

Commit

Permalink
feat ENG-1019: set default provider via system tray menu
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbreuninger committed Mar 3, 2023
1 parent 7a72186 commit 71a63b9
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 61 deletions.
3 changes: 2 additions & 1 deletion desktop/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod config;
mod constants;
pub mod constants;
pub use config::{DevpodCommandConfig, DevpodCommandError};

pub mod list_providers;
pub mod use_provider;
pub mod list_workspaces;
22 changes: 20 additions & 2 deletions desktop/src-tauri/src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use tauri::api::process::Command;
use thiserror::Error;

use crate::commands::constants::DEVPOD_BINARY_NAME;
Expand All @@ -21,6 +22,14 @@ impl<'a> CommandConfig<'_> {
pub enum DevpodCommandError {
#[error("unable to parse command response")]
Parse(#[from] serde_json::Error),
#[error("unable to find sidecar binary")]
Sidecar,
#[error("unable to collect output from command")]
Output,
#[error("command failed")]
Failed(#[from] tauri::api::Error),
#[error("command exited with non-zero code")]
Exit,
}
impl serde::Serialize for DevpodCommandError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand All @@ -30,13 +39,22 @@ impl serde::Serialize for DevpodCommandError {
serializer.serialize_str(self.to_string().as_ref())
}
}

pub trait DevpodCommandConfig<T> {
fn config(&self) -> CommandConfig {
CommandConfig {
binary_name: DEVPOD_BINARY_NAME,
args: vec![],
}
}
fn deserialize(&self, str: &str) -> Result<T, DevpodCommandError>;
fn exec(self) -> Result<T, DevpodCommandError>;

fn new_command(&self) -> Result<Command, DevpodCommandError> {
let config = self.config();

let cmd = Command::new_sidecar(config.binary_name())
.map_err(|_| DevpodCommandError::Sidecar)?
.args(config.args());

Ok(cmd)
}
}
3 changes: 2 additions & 1 deletion desktop/src-tauri/src/commands/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pub(super) const DEVPOD_BINARY_NAME: &str = "devpod";

// Commands
pub(super) const DEVPOD_COMMAND_LIST: &str = "list";
pub(super) const DEVPOD_COMMAND_PROVIDERS: &str = "provider";
pub(super) const DEVPOD_COMMAND_PROVIDER: &str = "provider";
pub(super) const DEVPOD_COMMAND_USE: &str = "use";

// Flags
pub(super) const OUTPUT_JSON_ARG: &str = "--output=json";
22 changes: 15 additions & 7 deletions desktop/src-tauri/src/commands/list_providers.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
use crate::providers::ProvidersState;

use super::{
config::{CommandConfig, DevpodCommandConfig, DevpodCommandError},
constants::{
DEVPOD_BINARY_NAME, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_PROVIDERS, OUTPUT_JSON_ARG,
DEVPOD_BINARY_NAME, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_PROVIDER, OUTPUT_JSON_ARG,
},
};
use crate::providers::ProvidersState;

pub struct ListProvidersCommand {}
impl ListProvidersCommand {
pub fn new() -> Self {
ListProvidersCommand {}
}

fn deserialize(&self, str: &str) -> Result<ProvidersState, DevpodCommandError> {
serde_json::from_str(str).map_err(|err| DevpodCommandError::Parse(err))
}
}
impl DevpodCommandConfig<ProvidersState> for ListProvidersCommand {
fn config(&self) -> CommandConfig {
fn config(&self) -> CommandConfig {
CommandConfig {
binary_name: DEVPOD_BINARY_NAME,
args: vec![
DEVPOD_COMMAND_PROVIDERS,
DEVPOD_COMMAND_PROVIDER,
DEVPOD_COMMAND_LIST,
OUTPUT_JSON_ARG,
],
}
}

fn deserialize(&self, str: &str) -> Result<ProvidersState, DevpodCommandError> {
serde_json::from_str(str).map_err(|err| DevpodCommandError::Parse(err))
fn exec(self) -> Result<ProvidersState, DevpodCommandError> {
let output = self
.new_command()?
.output()
.map_err(|_| DevpodCommandError::Output)?;

self.deserialize(&output.stdout)
}
}
16 changes: 12 additions & 4 deletions desktop/src-tauri/src/commands/list_workspaces.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use crate::workspaces::WorkspacesState;

use super::{
config::{CommandConfig, DevpodCommandConfig, DevpodCommandError},
constants::{DEVPOD_BINARY_NAME, DEVPOD_COMMAND_LIST, OUTPUT_JSON_ARG},
};
use crate::workspaces::WorkspacesState;

pub struct ListWorkspacesCommand {}
impl ListWorkspacesCommand {
pub fn new() -> Self {
ListWorkspacesCommand {}
}

fn deserialize(&self, str: &str) -> Result<WorkspacesState, DevpodCommandError> {
serde_json::from_str(str).map_err(|err| DevpodCommandError::Parse(err))
}
}
impl DevpodCommandConfig<WorkspacesState> for ListWorkspacesCommand {
fn config(&self) -> CommandConfig {
Expand All @@ -19,7 +22,12 @@ impl DevpodCommandConfig<WorkspacesState> for ListWorkspacesCommand {
}
}

fn deserialize(&self, str: &str) -> Result<WorkspacesState, DevpodCommandError> {
serde_json::from_str(str).map_err(|err| DevpodCommandError::Parse(err))
fn exec(self) -> Result<WorkspacesState, DevpodCommandError> {
let output = self
.new_command()?
.output()
.map_err(|_| DevpodCommandError::Output)?;

self.deserialize(&output.stdout)
}
}
35 changes: 35 additions & 0 deletions desktop/src-tauri/src/commands/use_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::{
config::{CommandConfig, DevpodCommandConfig, DevpodCommandError},
constants::{DEVPOD_BINARY_NAME, DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_USE},
};

pub struct UseProviderCommand<'a> {
new_provider_id: &'a str,
}
impl<'a> UseProviderCommand<'a> {
pub fn new(new_provider_id: &'a str) -> Self {
UseProviderCommand { new_provider_id }
}
}
impl DevpodCommandConfig<()> for UseProviderCommand<'_> {
fn config(&self) -> CommandConfig {
CommandConfig {
binary_name: DEVPOD_BINARY_NAME,
args: vec![
DEVPOD_COMMAND_PROVIDER,
DEVPOD_COMMAND_USE,
self.new_provider_id,
],
}
}

fn exec(self) -> Result<(), DevpodCommandError> {
let cmd = self.new_command()?;

cmd.status()
.map_err(|err| DevpodCommandError::Failed(err))?
.success()
.then(|| ())
.ok_or_else(|| DevpodCommandError::Exit)
}
}
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use tauri::{plugin::TauriPlugin, Wry};
use tauri_plugin_log::{LogLevel, LogTarget};
use tauri_plugin_log::{LogTarget};

pub fn build_plugin() -> TauriPlugin<Wry> {
let enable_debug_logging: Option<&'static str> = option_env!("DEBUG");
Expand Down
60 changes: 45 additions & 15 deletions desktop/src-tauri/src/providers.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::{
commands::{list_providers::ListProvidersCommand, DevpodCommandConfig, DevpodCommandError},
system_tray::ToSystemTraySubmenu,
commands::{
list_providers::ListProvidersCommand, use_provider::UseProviderCommand,
DevpodCommandConfig, DevpodCommandError,
},
system_tray::{SystemTray, SystemTrayClickHandler, ToSystemTraySubmenu},
};
use chrono::DateTime;
use log::trace;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri::{api::process::Command, CustomMenuItem, SystemTrayMenu, SystemTraySubmenu};
use tauri::{CustomMenuItem, SystemTrayMenu, SystemTraySubmenu};

#[derive(Serialize, Deserialize, Debug, Default, Eq, PartialEq)]
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
Expand All @@ -24,18 +27,9 @@ impl ProvidersState {
impl ProvidersState {
pub fn load() -> Result<ProvidersState, DevpodCommandError> {
trace!("loading providers");

let list_providers_cmd = ListProvidersCommand::new();
let config = list_providers_cmd.config();

// TODO: maybe refactor into `Command` type
let output = Command::new_sidecar(config.binary_name())
.expect("should have found `devpod` binary")
.args(config.args())
.output()
.expect("should have spawned `devpod`");

list_providers_cmd.deserialize(&output.stdout)
list_providers_cmd.exec()
}
}

Expand All @@ -57,8 +51,44 @@ impl ToSystemTraySubmenu for ProvidersState {
SystemTraySubmenu::new("Providers", providers_menu)
}

fn on_tray_item_clicked(&self, _id: &str) -> () {
todo!()
fn on_tray_item_clicked(&mut self, tray_item_id: &str) -> Option<SystemTrayClickHandler> {
// Make sure providers contain clicked item.
let provider = self
.providers
.iter()
.find(|(el_id, _)| Self::item_id(el_id) == tray_item_id);

// Don't proceed if default provider is the same as the selected.
if self.default_provider == provider.map(|(name, _)| name.clone()) {
return None;
}

if let Some(provider) = provider {
let provider_name = provider.0;
let use_provider_cmd = UseProviderCommand::new(provider_name);
let updated = use_provider_cmd.exec();

if updated.is_err() {
return None;
}

let provider_name = provider_name.to_string();
return Some(Box::new(move |app_handle, app_state| {
let tray_handle = app_handle.tray_handle();
let providers = &mut *app_state.providers.lock().unwrap();
providers.default_provider = Some(provider_name.to_string());

let workspaces = &*app_state.workspaces.lock().unwrap();
let new_menu = SystemTray::new()
.build_with_submenus(vec![Box::new(workspaces), Box::new(providers)]);

tray_handle
.set_menu(new_menu)
.expect("should be able to set menu");
}));
}

None
}
}

Expand Down
29 changes: 17 additions & 12 deletions desktop/src-tauri/src/system_tray.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use log::trace;
use log::warn;
use tauri::{
AppHandle, CustomMenuItem, Manager, SystemTray as TauriSystemTray, SystemTrayEvent,
AppHandle, CustomMenuItem, Manager, State, SystemTray as TauriSystemTray, SystemTrayEvent,
SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu, WindowBuilder, WindowUrl, Wry,
};

use crate::{providers::ProvidersState, workspaces::WorkspacesState, AppState};

pub trait SystemTrayIdentifier {}
pub type SystemTrayClickHandler = Box<dyn Fn(&AppHandle, State<AppState>) -> ()>;
pub trait ToSystemTraySubmenu {
fn to_submenu(&self) -> SystemTraySubmenu;
fn on_tray_item_clicked(&self, id: &str) -> ();
fn on_tray_item_clicked(&mut self, tray_item_id: &str) -> Option<SystemTrayClickHandler>;
}

pub struct SystemTray {}
Expand All @@ -25,8 +26,8 @@ impl SystemTray {
const SHOW_DASHBOARD_ID: &str = "show_dashboard";
}

// FIXME: should implement proper builder pattern
impl SystemTray {

pub fn build_menu(&self) -> SystemTrayMenu {
let show_dashboard = CustomMenuItem::new(Self::SHOW_DASHBOARD_ID, "Show Dashboard");
let quit = CustomMenuItem::new(Self::QUIT_ID, "Quit");
Expand Down Expand Up @@ -90,21 +91,25 @@ impl SystemTray {
}
id => {
let app_state = app.state::<AppState>();
let mut maybe_handler: Option<_> = None;

if id.starts_with(WorkspacesState::IDENTIFIER_PREFIX) {
let workspaces_state = &*app_state.workspaces.lock().unwrap();
workspaces_state.on_tray_item_clicked(id);

return;
let workspaces_state = &mut *app_state.workspaces.lock().unwrap();
maybe_handler = workspaces_state.on_tray_item_clicked(id);
} else if id.starts_with(ProvidersState::IDENTIFIER_PREFIX) {
let providers_state = &mut *app_state.providers.lock().unwrap();
maybe_handler = providers_state.on_tray_item_clicked(id);
} else {
warn!("Received unhandled click for ID: {}", id);
}
if id.starts_with(ProvidersState::IDENTIFIER_PREFIX) {
let providers_state = &*app_state.providers.lock().unwrap();
providers_state.on_tray_item_clicked(id);

if let Some(handler) = maybe_handler {
handler(app, app_state);

return;
}

trace!("Received unhandled click for ID: {}", id)
return;
}
},
_ => {}
Expand Down
7 changes: 4 additions & 3 deletions desktop/src-tauri/src/ui_ready.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ pub fn ui_ready(
}
});

// TODO: should synchronize with background loops if either one of them changed
// Initial update to system tray after setting up data pipelines.
let providers = &*state.providers.lock().unwrap();
let workspaces = &*state.workspaces.lock().unwrap();
let providers = ProvidersState::load().unwrap();
let workspaces = WorkspacesState::load().unwrap();

let new_menu =
SystemTray::new().build_with_submenus(vec![Box::new(workspaces), Box::new(providers)]);
SystemTray::new().build_with_submenus(vec![Box::new(&workspaces), Box::new(&providers)]);
tray_handle
.set_menu(new_menu)
.expect("should be able to set menu");
Expand Down
Loading

0 comments on commit 71a63b9

Please sign in to comment.