diff --git a/ui/src-tauri/src/app_state.rs b/ui/src-tauri/src/app_state.rs new file mode 100644 index 000000000..8db40a23e --- /dev/null +++ b/ui/src-tauri/src/app_state.rs @@ -0,0 +1,72 @@ +use std::fs::{File, OpenOptions}; +use std::io::prelude::*; +use std::path::PathBuf; +use serde::{Serialize, Deserialize}; +use tauri::api::path::home_dir; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct AgentList { + pub name: String, + pub path: PathBuf, + pub bootstrap: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LauncherState { + pub agent_list: Vec, + pub selected_agent: Option, +} + +impl LauncherState { + pub fn save(&mut self) -> std::io::Result<()> { + let path = home_dir().expect("Could not get home dir").join("ad4m-state.json"); + let mut file = File::create(&path)?; + let data = serde_json::to_string(&self).unwrap(); + file.write_all(data.as_bytes())?; + Ok(()) + } + + pub fn load() -> std::io::Result { + let path = home_dir().expect("Could not get home dir").join("ad4m-state.json"); + let mut file = OpenOptions::new().read(true).write(true).create(true).open(&path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + + let state = match serde_json::from_str(&data) { + Ok(state) => state, + Err(_) => { + let agent = AgentList { + name: "Main Net".to_string(), + path: PathBuf::from(home_dir().expect("Could not get home dir")).join(".ad4m".to_string()), + bootstrap: None + }; + + LauncherState { + agent_list: vec![{ + agent.clone() + }], + selected_agent: Some(agent) + } + } + }; + + Ok(state) + } + + pub fn add_agent(&mut self, agent: AgentList) { + if !self.is_agent_taken(&agent.name, &agent.path) { + self.agent_list.push(agent); + } + } + + pub fn remove_agent(&mut self, agent: AgentList) { + self.agent_list.retain(|a| a.name != agent.name && &a.path != &agent.path); + } + + pub fn is_agent_taken(&self, new_name: &str, new_path: &PathBuf) -> bool { + self.agent_list.iter().any(|agent| agent.name == new_name && (&agent.path == new_path)) + } +} + + + diff --git a/ui/src-tauri/src/commands/app.rs b/ui/src-tauri/src/commands/app.rs index 05caa99d8..5842d39a4 100644 --- a/ui/src-tauri/src/commands/app.rs +++ b/ui/src-tauri/src/commands/app.rs @@ -1,9 +1,11 @@ extern crate remove_dir_all; +use crate::app_state::{AgentList, LauncherState}; use crate::util::create_tray_message_windows; use crate::{config::data_path, get_main_window}; use remove_dir_all::*; +use tauri::api::path::home_dir; use tauri::{LogicalSize, Manager}; use tauri::Size; use tauri_plugin_positioner::{Position, WindowExt}; @@ -38,6 +40,44 @@ pub fn open_tray(app_handle: tauri::AppHandle) { } } +#[tauri::command] +pub fn add_app_agent_state(agent: AgentList) { + let mut state = LauncherState::load().unwrap(); + + let mut new_agent = agent.clone(); + + new_agent.path = home_dir().unwrap().join(agent.path); + + state.add_agent(new_agent); + + state.save().unwrap(); +} + +#[tauri::command] +pub fn remove_app_agent_state(agent: AgentList) { + let mut state = LauncherState::load().unwrap(); + + state.remove_agent(agent.clone()); + + state.save().unwrap(); +} + +#[tauri::command] +pub fn set_selected_agent(agent: AgentList) { + let mut state = LauncherState::load().unwrap(); + + state.selected_agent = Some(agent); + + state.save().unwrap(); +} + +#[tauri::command] +pub fn get_app_agent_list() -> Option { + let state = LauncherState::load().unwrap(); + + serde_json::to_string(&state).ok() +} + #[tauri::command] #[cfg(feature = "custom-protocol")] pub fn open_tray_message(app_handle: tauri::AppHandle) { diff --git a/ui/src-tauri/src/config.rs b/ui/src-tauri/src/config.rs index 2b9fd1b99..df1c18ea1 100644 --- a/ui/src-tauri/src/config.rs +++ b/ui/src-tauri/src/config.rs @@ -6,10 +6,18 @@ pub fn data_path() -> PathBuf { home_dir().expect("Could not get home dir").join(".ad4m") } +pub fn data_dev_path() -> PathBuf { + home_dir().expect("Could not get home dir").join(".ad4m-dev") +} + pub fn log_path() -> PathBuf { data_path().join("ad4m.log") } +pub fn log_dev_path() -> PathBuf { + data_path().join("ad4m.log") +} + #[cfg(feature = "custom-protocol")] pub fn app_url() -> String { "index.html".to_string() diff --git a/ui/src-tauri/src/main.rs b/ui/src-tauri/src/main.rs index 7ad2f3eeb..4313b59b0 100644 --- a/ui/src-tauri/src/main.rs +++ b/ui/src-tauri/src/main.rs @@ -43,12 +43,14 @@ mod util; mod system_tray; mod menu; mod commands; +mod app_state; use tauri::Manager; +use crate::app_state::LauncherState; use crate::commands::proxy::{get_proxy, login_proxy, setup_proxy, stop_proxy}; use crate::commands::state::{get_port, request_credential}; -use crate::commands::app::{close_application, close_main_window, clear_state, open_tray, open_tray_message, open_dapp}; -use crate::config::data_path; +use crate::commands::app::{add_app_agent_state, get_app_agent_list, remove_app_agent_state, set_selected_agent, close_application, close_main_window, clear_state, open_tray, open_tray_message, open_dapp}; +use crate::config::{data_dev_path, data_path}; use crate::config::log_path; use crate::util::find_port; use crate::menu::{handle_menu_event, open_logs_folder}; @@ -109,11 +111,17 @@ fn rlim_execute() { fn main() { env::set_var("RUST_LOG", "holochain=warn,wasmer_compiler_cranelift=warn,rust_executor=info,warp::server"); + let state = LauncherState::load().unwrap(); + + let selected_agent = state.selected_agent.clone().unwrap(); + let app_path = selected_agent.path; + let bootstrap_path = selected_agent.bootstrap; + #[cfg(not(target_os = "windows"))] rlim_execute(); - if !data_path().exists() { - let _ = fs::create_dir_all(data_path()); + if !app_path.exists() { + let _ = fs::create_dir_all(app_path.clone()); } if log_path().exists() { @@ -170,8 +178,8 @@ fn main() { save_executor_port(free_port); match rust_executor::init::init( - Some(String::from(data_path().to_str().unwrap())), - None + Some(String::from(app_path.to_str().unwrap())), + bootstrap_path.and_then(|path| path.to_str().map(|s| s.to_string())), ) { Ok(()) => { println!("Ad4m initialized sucessfully"); @@ -208,7 +216,11 @@ fn main() { clear_state, open_tray, open_tray_message, - open_dapp + open_dapp, + add_app_agent_state, + get_app_agent_list, + set_selected_agent, + remove_app_agent_state ]) .setup(move |app| { // Hides the dock icon @@ -225,7 +237,7 @@ fn main() { let mut config = Ad4mConfig::default(); config.admin_credential = Some(req_credential.to_string()); - config.app_data_path = Some(String::from(data_path().to_str().unwrap())); + config.app_data_path = Some(String::from(app_path.to_str().unwrap())); config.gql_port = Some(free_port); config.network_bootstrap_seed = None; config.run_dapp_server = Some(true); diff --git a/ui/src/components/Settings.tsx b/ui/src/components/Settings.tsx index 90de84d33..bc8c949b2 100644 --- a/ui/src/components/Settings.tsx +++ b/ui/src/components/Settings.tsx @@ -57,11 +57,17 @@ const Profile = (props: Props) => { methods: { toggleExpertMode }, } = useContext(Ad4minContext); + const [appState, setAppState] = useState({} as any); + const [trustedAgents, setTrustedAgents] = useState([]); const [trustedAgentModalOpen, settrustedAgentModalOpen] = useState(false); const [clearAgentModalOpen, setClearAgentModalOpen] = useState(false); + const [showAgentSelection, setShowAgentSelection] = useState(false); + const [createAgent, setCreateAgent] = useState(false); + const [newAgentName, setNewAgentName] = useState(""); + const [file, setFile] = useState(null); const [proxy, setProxy] = useState(""); @@ -89,6 +95,11 @@ const Profile = (props: Props) => { setPassword(value); }; + const getAppState = useCallback(async () => { + const state = await invoke("get_app_agent_list"); + setAppState(JSON.parse(state)) + }, []); + const getTrustedAgents = useCallback(async () => { if (url) { const client = await buildAd4mClient(url, false); @@ -126,7 +137,8 @@ const Profile = (props: Props) => { useEffect(() => { fetchCurrentAgentProfile(); getTrustedAgents(); - }, [fetchCurrentAgentProfile, getTrustedAgents]); + getAppState(); + }, [fetchCurrentAgentProfile, getTrustedAgents, getAppState]); useEffect(() => { const getProxy = async () => { @@ -195,6 +207,19 @@ const Profile = (props: Props) => { } }; + const creatAgentFunc = async () => { + await invoke("add_app_agent_state", { + agent: { + name: newAgentName, + path: `.${newAgentName.toLowerCase()}`, + bootstrap: file, + } + }); + + setCreateAgent(false); + getAppState(); + } + return (
@@ -260,6 +285,13 @@ const Profile = (props: Props) => { + + setShowAgentSelection(true)} full variant="secondary"> + + Select agent + + + setClearAgentModalOpen(true)} @@ -331,6 +363,87 @@ const Profile = (props: Props) => { )} + {showAgentSelection && ( + setShowAgentSelection(e.target.open)} + > + + + + Select agent + + + + Disclaimer: After changing the agent you will have to restart the launcher + for it to start using the new agent + + + { + appState.agent_list.map((agent: any) => ( + <> + { + invoke("set_selected_agent", { agent }); + getAppState(); + setShowAgentSelection(false); + }} + > + {agent.name} + + + + )) + } + setCreateAgent(true)}> + + Add new agent + + + + )} + + { + createAgent && ( + setCreateAgent(e.target.open)} + > + + + + Create new agent + + + setNewAgentName(e.target.value)} + required> + + setFile(e.target.value)} + required + > + + + Create Agent + + + + ) + } + {clearAgentModalOpen && (