From d7a273506a6d3f453aa2b872c9838d38b76eea24 Mon Sep 17 00:00:00 2001 From: Ryan Killeen Date: Wed, 16 Mar 2022 22:55:50 -0400 Subject: [PATCH] feat(settings): add custom config folder selector chore: update config type to be more specific 0.5.0 --- package-lock.json | 4 +- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/app_config.rs | 81 +++++++++++++++++++++ src-tauri/src/config.rs | 53 -------------- src-tauri/src/main.rs | 111 ++++++++++++++++++++--------- src-tauri/src/user_config.rs | 46 ++++++++++++ src-tauri/tauri.conf.json | 2 +- src/Config.tsx | 28 ++------ src/components/Filepath.tsx | 29 ++++++++ src/components/Omnibar/Omnibar.tsx | 2 +- src/components/Shortcut.tsx | 3 +- src/forms/workflow-settings.tsx | 16 ++--- src/utils.ts | 14 ++-- 15 files changed, 263 insertions(+), 132 deletions(-) create mode 100644 src-tauri/src/app_config.rs delete mode 100644 src-tauri/src/config.rs create mode 100644 src-tauri/src/user_config.rs create mode 100644 src/components/Filepath.tsx diff --git a/package-lock.json b/package-lock.json index 613f909..fe70aae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "brancato", - "version": "0.4.1", + "version": "0.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "brancato", - "version": "0.4.1", + "version": "0.5.0", "dependencies": { "@algolia/autocomplete-js": "^1.5.3", "@algolia/autocomplete-theme-classic": "^1.5.3", diff --git a/package.json b/package.json index 0fc8fe3..521caa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "brancato", - "version": "0.4.1", + "version": "0.5.0", "private": true, "dependencies": { "@algolia/autocomplete-js": "^1.5.3", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a364733..f9586e0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -274,7 +274,7 @@ dependencies = [ [[package]] name = "brancato" -version = "0.4.1" +version = "0.5.0" dependencies = [ "directories", "execute", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cdfcd49..0e2ffb8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "brancato" -version = "0.4.1" +version = "0.5.0" description = "A tool for stage-managing your life" authors = ["Ryan Killeen"] license = "" diff --git a/src-tauri/src/app_config.rs b/src-tauri/src/app_config.rs new file mode 100644 index 0000000..ac30669 --- /dev/null +++ b/src-tauri/src/app_config.rs @@ -0,0 +1,81 @@ +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +const USER_CONFIG_FILE_NAME: &str = "config.json"; +const APP_CONFIG_FILE_NAME: &str = "app-config.json"; +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct AppConfig { + #[serde(default = "default_user_config_path")] + pub user_config_path: PathBuf, +} + +impl Default for AppConfig { + fn default() -> Self { + let default_path = default_user_config_path(); + AppConfig { + user_config_path: default_path, + } + } +} + +fn default_dir() -> PathBuf { + ProjectDirs::from("", "", "Brancato") + .unwrap() + .config_dir() + .to_owned() +} + +fn default_user_config_path() -> PathBuf { + default_dir().join(USER_CONFIG_FILE_NAME) +} + +fn default_app_config_path() -> PathBuf { + default_dir().join(APP_CONFIG_FILE_NAME) +} + +fn create_app_config(config: Option) -> AppConfig { + let default = match config { + Some(c) => c, + None => AppConfig::default(), + }; + let data = serde_json::to_string(&default).expect("Unable to parse struct"); + let path = default_dir().join(APP_CONFIG_FILE_NAME); + let prefix = path.parent().unwrap(); + fs::create_dir_all(prefix).unwrap(); + fs::write(path, data).expect("Unable to write file"); + + return default; +} + +pub fn get_or_create_app_config() -> AppConfig { + let read_path = default_app_config_path(); + let config_file = fs::read_to_string(&read_path); + let config: AppConfig = match config_file { + Ok(file) => serde_json::from_str(&file).unwrap(), + Err(_) => create_app_config(None), + }; + return config; +} + +pub fn set_custom_user_config_path( + new_user_config_path: PathBuf, +) -> Result { + let new_user_config_path = new_user_config_path.join(USER_CONFIG_FILE_NAME); + + let current_config = get_or_create_app_config(); + + let new_config = AppConfig { + user_config_path: new_user_config_path, + ..current_config + }; + + fs::copy( + ¤t_config.user_config_path, + new_config.user_config_path.clone(), + ) + .and_then(|_| fs::remove_file(current_config.user_config_path))?; + + Ok(create_app_config(Some(new_config.clone()))) +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs deleted file mode 100644 index fc164be..0000000 --- a/src-tauri/src/config.rs +++ /dev/null @@ -1,53 +0,0 @@ -use directories::ProjectDirs; -use serde::{Deserialize, Serialize}; -use std::fs; - -use super::workflows::Workflow; - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct Config { - pub workflows: Vec, - #[serde(default = "default_shortcut")] - pub shortcut: String, -} - -impl Default for Config { - fn default() -> Self { - Config { - workflows: Vec::new(), - shortcut: "alt + m".to_string(), - } - } -} - -fn default_shortcut() -> String { - "alt + m".to_string() -} - -pub fn get_config() -> Config { - if let Some(proj_dirs) = ProjectDirs::from("", "", "Brancato") { - let config_dir = proj_dirs.config_dir(); - let read_path = config_dir.join("config.json"); - - let config_file = fs::read_to_string(&read_path); - let config: Config = match config_file { - Ok(file) => serde_json::from_str(&file).unwrap(), - Err(_) => Config::default(), - }; - - return config; - } else { - Config::default() - } -} - -pub fn set_config(config: &Config) { - if let Some(proj_dirs) = ProjectDirs::from("", "", "Brancato") { - let config_dir = proj_dirs.config_dir(); - let data = serde_json::to_string(config).expect("Unable to parse struct"); - let path = config_dir.join("config.json"); - let prefix = path.parent().unwrap(); - fs::create_dir_all(prefix).unwrap(); - fs::write(path, data).expect("Unable to write file"); - } -} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 411d1c0..403b026 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,36 +3,36 @@ windows_subsystem = "windows" )] -mod config; +mod app_config; +mod user_config; mod windows; mod workflows; -use std::{env, sync::Mutex}; +use app_config::{set_custom_user_config_path, AppConfig}; +use serde::Serialize; +use std::{env, path::PathBuf, sync::Mutex}; use tauri::{ - AppHandle, CustomMenuItem, GlobalShortcutManager, Manager, RunEvent, State, SystemTray, - SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, + api::dialog::blocking::FileDialogBuilder, AppHandle, CustomMenuItem, GlobalShortcutManager, + Manager, RunEvent, State, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, }; -use config::Config; +use user_config::{set_user_config, UserConfig}; use windows::focus_window; use workflows::run_step; -#[derive(Default)] -struct AppState(Mutex); - -fn _get_state(state: State) -> Config { - let config_mutex = state.0.lock().expect("Could not lock mutex"); - let config = config_mutex.clone(); - config +#[derive(Default, Serialize)] +struct AppState { + user_config: UserConfig, + app_config: AppConfig, } -fn update_config_and_state( +fn update_user_config_and_state( app: &AppHandle, - state: State, - new_config: Config, + user_config: State>, + new_config: UserConfig, ) -> Result<(), tauri::Error> { - let mut app_state = state.0.lock().expect("Could not lock mutex"); - config::set_config(&new_config); + let mut app_state = user_config.lock().expect("Could not lock mutex"); + set_user_config(&new_config); *app_state = new_config; app @@ -43,24 +43,59 @@ fn update_config_and_state( } #[tauri::command] -fn save_workflows( - state: State, +fn save_user_config( + state: State>, app: AppHandle, - config: Config, + config: UserConfig, ) -> Result<(), tauri::Error> { - update_config_and_state(&app, state, config).ok(); + update_user_config_and_state(&app, state, config).ok(); Ok(()) } #[tauri::command] -fn get_state(state: State) -> Config { - _get_state(state) +fn get_state( + user_config_state: State>, + app_config_state: State>, +) -> AppState { + let user_config = user_config_state + .lock() + .expect("Could not lock mutex") + .clone(); + + let app_config = app_config_state + .lock() + .expect("Couldn't lock mutex") + .clone(); + + let state = AppState { + user_config, + app_config, + }; + return state; +} + +#[tauri::command] +fn set_user_config_path(app_config_state: State>) -> Option { + let folder_path = FileDialogBuilder::new().pick_folder(); + + match folder_path { + Some(path) => match set_custom_user_config_path(path.clone()) { + Ok(updated_config) => { + let mut state = app_config_state.lock().expect("Couldn't lock"); + + *state = updated_config; + Some(path) + } + Err(_) => None, + }, + None => None, + } } #[tauri::command] -async fn run_workflow(state: State<'_, AppState>, label: String) -> Result<(), ()> { - let current_state = _get_state(state); +async fn run_workflow(state: State<'_, Mutex>, label: String) -> Result<(), ()> { + let current_state = state.lock().expect("Can't unlock").clone(); let mut workflow = current_state .workflows @@ -85,12 +120,12 @@ async fn open_settings(app: AppHandle) -> Result<(), tauri::Error> { #[tauri::command] async fn set_shortcut( app: AppHandle, - state: State<'_, AppState>, + user_config: State<'_, Mutex>, shortcut: String, ) -> Result<(), tauri::Error> { - let config = _get_state(state.clone()); + let config = user_config.lock().expect("Could not lock mutex").clone(); - let new_config = Config { + let new_config = UserConfig { shortcut: shortcut.to_owned(), ..config.to_owned() }; @@ -107,7 +142,7 @@ async fn set_shortcut( }) .ok(); - update_config_and_state(app_ref, state, new_config).ok(); + update_user_config_and_state(app_ref, user_config, new_config).ok(); Ok(()) } @@ -119,7 +154,8 @@ fn open_omnibar(app: &AppHandle) -> Result<(), tauri::Error> { Ok(()) } fn main() { - let user_config = config::get_config(); + let app_config = app_config::get_or_create_app_config(); + let user_config = user_config::get_user_config(app_config.user_config_path.clone()); let quit = CustomMenuItem::new("quit", "Quit"); let hide = CustomMenuItem::new("hide", "Hide"); @@ -173,13 +209,15 @@ fn main() { }, _ => {} }) - .manage(AppState(Mutex::new(user_config))) + .manage(Mutex::new(user_config)) + .manage(Mutex::new(app_config)) .invoke_handler(tauri::generate_handler![ get_state, - save_workflows, + save_user_config, run_workflow, open_settings, - set_shortcut + set_shortcut, + set_user_config_path ]) .build(tauri::generate_context!()) .expect("error while running tauri application"); @@ -188,7 +226,12 @@ fn main() { // Application is ready (triggered only once) RunEvent::Ready => { let app_handle = app_handle.clone(); - let startup_shortcut = _get_state(app_handle.state::()).shortcut; + let startup_shortcut = app_handle + .state::>() + .lock() + .expect("Could not lock mutex") + .clone() + .shortcut; app_handle .global_shortcut_manager() diff --git a/src-tauri/src/user_config.rs b/src-tauri/src/user_config.rs new file mode 100644 index 0000000..45bc0a3 --- /dev/null +++ b/src-tauri/src/user_config.rs @@ -0,0 +1,46 @@ +use crate::app_config::get_or_create_app_config; + +use super::workflows::Workflow; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct UserConfig { + pub workflows: Vec, + #[serde(default = "default_shortcut")] + pub shortcut: String, +} + +impl Default for UserConfig { + fn default() -> Self { + UserConfig { + workflows: Vec::new(), + shortcut: "alt + m".to_string(), + } + } +} + +fn default_shortcut() -> String { + "alt + m".to_string() +} + +pub fn get_user_config(config_path: PathBuf) -> UserConfig { + let config_file = fs::read_to_string(&config_path); + match config_file { + Ok(file) => serde_json::from_str(&file).unwrap(), + Err(_) => { + let default = UserConfig::default(); + set_user_config(&default); + return default; + } + } +} + +pub fn set_user_config(config: &UserConfig) { + let config_path = get_or_create_app_config().user_config_path; + let data = serde_json::to_string(config).expect("Unable to parse struct"); + let prefix = config_path.parent().unwrap(); + fs::create_dir_all(prefix).unwrap(); + fs::write(config_path, data).expect("Unable to write file"); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4ec9696..9e8130b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "brancato", - "version": "0.4.1" + "version": "0.5.0" }, "build": { "distDir": "../build", diff --git a/src/Config.tsx b/src/Config.tsx index 543bb98..254ca83 100644 --- a/src/Config.tsx +++ b/src/Config.tsx @@ -2,6 +2,7 @@ import "./App.css"; import Shortcut from "./components/Shortcut"; import * as Tabs from "@radix-ui/react-tabs"; import WorkflowSettings from "./forms/workflow-settings"; +import Filepath from "./components/Filepath"; export type Workflow = { name: string; @@ -10,7 +11,7 @@ export type Workflow = { }[]; }; -export type Workflows = { +export type UserConfig = { workflows: Workflow[]; shortcut: string; }; @@ -26,26 +27,9 @@ enum TabSections { } function Config() { - // const addFilePath = () => { - // dialog.open({ multiple: false }).then((data) => { - // console.log(data); - // let path = data as string; - // setPathFromFile(path); - // }); - // }; - // useEffect(() => { - // if (pathFromFile) { - // append({ - // path: pathFromFile, - // }); - // setPathFromFile(undefined); - // } - // }, [pathFromFile, append]); - return (
- @@ -60,15 +44,11 @@ function Config() { +
+
- {/* */} - {/* {(!process.env.NODE_ENV || process.env.NODE_ENV === "development") && ( - - )} */}
); } diff --git a/src/components/Filepath.tsx b/src/components/Filepath.tsx new file mode 100644 index 0000000..467f2c1 --- /dev/null +++ b/src/components/Filepath.tsx @@ -0,0 +1,29 @@ +import { invoke } from "@tauri-apps/api"; +import { useEffect, useState } from "react"; +import { AppState, Commands, getConfig } from "../utils"; +const Filepath = () => { + const [appState, setAppState] = useState(); + + const updateState = () => { + getConfig().then((data) => setAppState(data)); + }; + useEffect(() => { + updateState(); + }, []); + + const updateConfigPath = () => { + invoke(Commands.UpdateConfigPath).then(updateState); + }; + return ( + <> + +
+ +
+ + ); +}; + +export default Filepath; diff --git a/src/components/Omnibar/Omnibar.tsx b/src/components/Omnibar/Omnibar.tsx index cb5bf7e..eb70370 100644 --- a/src/components/Omnibar/Omnibar.tsx +++ b/src/components/Omnibar/Omnibar.tsx @@ -45,7 +45,7 @@ const Omnibar = () => { const [suggestions, setSuggestions] = useState([]); async function setStoredConfigChoices() { let state = await getConfig(); - setSuggestions(state.workflows.map((wf) => wf.name)); + setSuggestions(state.user_config.workflows.map((wf) => wf.name)); } useEffect(() => { const unlisten1 = appWindow.listen( diff --git a/src/components/Shortcut.tsx b/src/components/Shortcut.tsx index 791b428..b7fd0d2 100644 --- a/src/components/Shortcut.tsx +++ b/src/components/Shortcut.tsx @@ -9,7 +9,7 @@ const Shortcut = () => { const [editingShortcut, setEditingShortcut] = useState(false); const [listenForKeys, setListenForKeys] = useState(false); useEffect(() => { - getConfig().then((data) => setShortcut(data.shortcut)); + getConfig().then((data) => setShortcut(data.user_config.shortcut)); }, []); useEffect(() => { if (listenForKeys) { @@ -35,6 +35,7 @@ const Shortcut = () => { setEditingShortcut(false); invoke(Commands.SetShortcut, { shortcut }); }; + return (

diff --git a/src/forms/workflow-settings.tsx b/src/forms/workflow-settings.tsx index 1c846f9..60b93bb 100644 --- a/src/forms/workflow-settings.tsx +++ b/src/forms/workflow-settings.tsx @@ -1,33 +1,31 @@ import { invoke } from "@tauri-apps/api"; import { useState, useEffect } from "react"; import { useForm, useFormState } from "react-hook-form"; -import { Workflows } from "../Config"; +import { UserConfig } from "../Config"; import useWarningOnExit from "../hooks/use-warning-on-unload"; import { Commands, getConfig } from "../utils"; import WorkflowArray from "./workflow-array"; const WorkflowSettings = () => { - // const [pathFromFile, setPathFromFile] = useState(); - - const [defaultValues, setDefaultValues] = useState(); + const [defaultValues, setDefaultValues] = useState(); const { control, register, handleSubmit, getValues, setValue, reset } = - useForm({ + useForm({ defaultValues, }); const { isDirty, isSubmitting } = useFormState({ control }); useEffect(() => { - getConfig().then((data) => setDefaultValues(data)); + getConfig().then((data) => setDefaultValues(data.user_config)); }, []); // eslint-disable-next-line react-hooks/exhaustive-deps - const onSubmit = (data: Workflows) => { - invoke(Commands.SaveWorkflows, { config: data }); + const onSubmit = (data: UserConfig) => { + invoke(Commands.SaveUserConfig, { config: data }); }; useWarningOnExit(isDirty); - // + useEffect(() => { reset(defaultValues); }, [defaultValues, reset]); diff --git a/src/utils.ts b/src/utils.ts index 320a497..309d5bc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import { invoke } from "@tauri-apps/api"; -import { Workflows } from "./Config"; +import { UserConfig } from "./Config"; export enum AppEvents { OmnibarFocused = "omnibar-focus", @@ -11,10 +11,16 @@ export enum Commands { SetShortcut = "set_shortcut", OpenSettings = "open_settings", GetState = "get_state", - SaveWorkflows = "save_workflows", + SaveUserConfig = "save_user_config", + UpdateConfigPath = "set_user_config_path", } +type AppConfig = { + user_config_path: String; +}; + +export type AppState = { user_config: UserConfig; app_config: AppConfig }; + export const getConfig = async () => { - const test = (await invoke(Commands.GetState)) as Workflows; - return test; + return (await invoke(Commands.GetState)) as AppState; };