ugly but working unified settings store.
This commit is contained in:
parent
5bddf49f88
commit
9b5f1c5a04
5 changed files with 197 additions and 164 deletions
|
|
@ -82,7 +82,10 @@ pub fn run() {
|
|||
}
|
||||
_ => {}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![shortcut::set_binding])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
shortcut::change_binding,
|
||||
shortcut::reset_binding
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tauri::{App, AppHandle, Runtime};
|
||||
use tauri_plugin_store::{Store, StoreExt};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)] // Clone is useful
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ShortcutBinding {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
|
|
@ -10,28 +12,106 @@ pub struct ShortcutBinding {
|
|||
pub current_binding: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
/* still handy for composing the initial JSON in the store ------------- */
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AppSettings {
|
||||
pub bindings: Vec<ShortcutBinding>,
|
||||
pub bindings: HashMap<String, ShortcutBinding>,
|
||||
}
|
||||
|
||||
pub const SETTINGS_STORE_PATH: &str = "settings_store.json";
|
||||
|
||||
pub fn get_default_settings() -> AppSettings {
|
||||
AppSettings {
|
||||
bindings: vec![
|
||||
ShortcutBinding {
|
||||
id: "transcribe".to_string(),
|
||||
name: "Transcribe".to_string(),
|
||||
description: "Converts your speech into text.".to_string(),
|
||||
default_binding: "alt+space".to_string(),
|
||||
current_binding: "alt+space".to_string(),
|
||||
},
|
||||
ShortcutBinding {
|
||||
id: "test".to_string(),
|
||||
name: "Test".to_string(),
|
||||
description: "This is a test binding.".to_string(),
|
||||
default_binding: "ctrl+d".to_string(),
|
||||
current_binding: "ctrl+d".to_string(),
|
||||
},
|
||||
],
|
||||
}
|
||||
let mut bindings = HashMap::new();
|
||||
bindings.insert(
|
||||
"transcribe".to_string(),
|
||||
ShortcutBinding {
|
||||
id: "transcribe".to_string(),
|
||||
name: "Transcribe".to_string(),
|
||||
description: "Converts your speech into text.".to_string(),
|
||||
default_binding: "alt+space".to_string(),
|
||||
current_binding: "alt+space".to_string(),
|
||||
},
|
||||
);
|
||||
bindings.insert(
|
||||
"test".to_string(),
|
||||
ShortcutBinding {
|
||||
id: "test".to_string(),
|
||||
name: "Test".to_string(),
|
||||
description: "This is a test binding.".to_string(),
|
||||
default_binding: "ctrl+d".to_string(),
|
||||
current_binding: "ctrl+d".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
AppSettings { bindings }
|
||||
}
|
||||
|
||||
pub fn load_or_create_app_settings(app: &App) -> AppSettings {
|
||||
// Initialize store
|
||||
let store = app
|
||||
.store(SETTINGS_STORE_PATH)
|
||||
.expect("Failed to initialize store");
|
||||
|
||||
let settings = if let Some(settings_value) = store.get("settings") {
|
||||
// Parse the entire settings object
|
||||
match serde_json::from_value::<AppSettings>(settings_value) {
|
||||
Ok(settings) => {
|
||||
println!("Found existing settings: {:?}", settings);
|
||||
|
||||
settings
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to parse settings: {}", e);
|
||||
// Fall back to default settings if parsing fails
|
||||
let default_settings = get_default_settings();
|
||||
|
||||
// Store the default settings
|
||||
store.set("settings", serde_json::to_value(&default_settings).unwrap());
|
||||
|
||||
default_settings
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create default settings
|
||||
let default_settings = get_default_settings();
|
||||
|
||||
// Store the settings
|
||||
store.set("settings", serde_json::to_value(&default_settings).unwrap());
|
||||
|
||||
default_settings
|
||||
};
|
||||
|
||||
settings
|
||||
}
|
||||
|
||||
pub fn get_settings(app: &AppHandle) -> AppSettings {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE_PATH)
|
||||
.expect("Failed to initialize store");
|
||||
|
||||
let settings = serde_json::from_value::<AppSettings>(store.get("settings").unwrap()).unwrap();
|
||||
|
||||
settings
|
||||
}
|
||||
|
||||
pub fn write_settings(app: &AppHandle, settings: AppSettings) {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE_PATH)
|
||||
.expect("Failed to initialize store");
|
||||
|
||||
store.set("settings", serde_json::to_value(&settings).unwrap());
|
||||
}
|
||||
|
||||
pub fn get_bindings(app: &AppHandle) -> HashMap<String, ShortcutBinding> {
|
||||
let settings = get_settings(app);
|
||||
|
||||
settings.bindings
|
||||
}
|
||||
|
||||
pub fn get_stored_binding(app: &AppHandle, id: &str) -> ShortcutBinding {
|
||||
let bindings = get_bindings(app);
|
||||
|
||||
let binding = bindings.get(id).unwrap().clone();
|
||||
|
||||
binding
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,14 @@ use std::sync::Arc;
|
|||
use tauri::App;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Manager;
|
||||
use tauri::Runtime;
|
||||
use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
||||
use tauri_plugin_global_shortcut::{Shortcut, ShortcutState};
|
||||
use tauri_plugin_store::StoreExt;
|
||||
|
||||
use crate::managers::audio::AudioRecordingManager;
|
||||
use crate::managers::transcription::TranscriptionManager;
|
||||
use crate::settings;
|
||||
use crate::settings::AppSettings;
|
||||
use crate::settings::ShortcutBinding;
|
||||
use crate::utils;
|
||||
use crate::AppState;
|
||||
|
||||
fn transcribe_pressed(app: &AppHandle) {
|
||||
let rm = app.state::<Arc<AudioRecordingManager>>();
|
||||
|
|
@ -41,142 +37,68 @@ fn transcribe_released(app: &AppHandle) {
|
|||
}
|
||||
|
||||
pub fn init_shortcuts(app: &App) {
|
||||
// Initialize store
|
||||
let kb_store = app
|
||||
.store("settings_store.json")
|
||||
.expect("Failed to initialize store");
|
||||
|
||||
// Get or create settings
|
||||
let settings = if let Some(settings_value) = kb_store.get("settings") {
|
||||
// Parse the entire settings object
|
||||
match serde_json::from_value::<AppSettings>(settings_value) {
|
||||
Ok(settings) => {
|
||||
println!("Found existing settings: {:?}", settings);
|
||||
|
||||
settings
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to parse settings: {}", e);
|
||||
// Fall back to default settings if parsing fails
|
||||
let default_settings = settings::get_default_settings();
|
||||
|
||||
// Store the default settings
|
||||
kb_store.set("settings", serde_json::to_value(&default_settings).unwrap());
|
||||
|
||||
default_settings
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create default settings
|
||||
let default_settings = settings::get_default_settings();
|
||||
|
||||
// Store the settings
|
||||
kb_store.set("settings", serde_json::to_value(&default_settings).unwrap());
|
||||
|
||||
default_settings
|
||||
};
|
||||
let settings = settings::load_or_create_app_settings(app);
|
||||
|
||||
// Register shortcuts with the bindings from settings
|
||||
_register_shortcuts(app, settings.bindings);
|
||||
for (_id, binding) in settings.bindings {
|
||||
_register_shortcut(app.handle(), binding);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_binding(app: AppHandle, id: String, binding: String) {
|
||||
let app_state = app.state::<AppState>();
|
||||
let mut active_bindings = app_state.active_bindings.lock().unwrap();
|
||||
let old_binding_str = active_bindings.get(&id).cloned(); // Clone to avoid borrowing issues
|
||||
pub fn change_binding(app: AppHandle, id: String, binding: String) -> ShortcutBinding {
|
||||
let mut settings = settings::get_settings(&app);
|
||||
|
||||
match binding.parse::<Shortcut>() {
|
||||
Ok(shortcut) => {
|
||||
println!("Shortcut '{}' is valid", shortcut);
|
||||
// unregister the existing binding
|
||||
if let Some(old_binding_str) = old_binding_str {
|
||||
match old_binding_str.parse::<Shortcut>() {
|
||||
Ok(old_binding) => {
|
||||
app.global_shortcut()
|
||||
.unregister(old_binding)
|
||||
.expect("Failed to unregister shortcut");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing old shortcut '{}': {:?}", old_binding_str, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No existing shortcut to unregister");
|
||||
}
|
||||
// Get the binding to modify
|
||||
let binding_to_modify = settings.bindings.get(&id).unwrap().clone();
|
||||
|
||||
// register the new binding
|
||||
app.global_shortcut()
|
||||
.on_shortcut(shortcut, move |handler_app, scut, event| {
|
||||
if scut == &shortcut {
|
||||
println!("Global Shortcut pressed! {}", scut.into_string());
|
||||
if event.state == ShortcutState::Pressed {
|
||||
transcribe_pressed(handler_app);
|
||||
} else if event.state == ShortcutState::Released {
|
||||
transcribe_released(handler_app);
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("couldnt register shortcut");
|
||||
// Unregister the existing binding
|
||||
_unregister_shortcut(&app, binding_to_modify.clone());
|
||||
|
||||
// TODO error handling?
|
||||
active_bindings.insert(id, binding);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing shortcut '{}': {:?}", binding, e);
|
||||
}
|
||||
}
|
||||
// Create an updated binding
|
||||
let mut updated_binding = binding_to_modify;
|
||||
updated_binding.current_binding = binding;
|
||||
|
||||
// Register the new binding
|
||||
_register_shortcut(&app, updated_binding.clone());
|
||||
|
||||
// Update the binding in the settings
|
||||
settings.bindings.insert(id, updated_binding.clone());
|
||||
|
||||
// Save the settings
|
||||
settings::write_settings(&app, settings);
|
||||
|
||||
// Return the updated binding
|
||||
updated_binding
|
||||
}
|
||||
|
||||
fn _register_shortcuts(app: &App, bindings: Vec<ShortcutBinding>) {
|
||||
// get bindings from state
|
||||
let app_state = app.state::<AppState>();
|
||||
let mut active_bindings = app_state.active_bindings.lock().unwrap();
|
||||
#[tauri::command]
|
||||
pub fn reset_binding(app: AppHandle, id: String) -> ShortcutBinding {
|
||||
let binding = settings::get_stored_binding(&app, &id);
|
||||
return change_binding(app, id, binding.default_binding);
|
||||
}
|
||||
|
||||
// iterate through bindings
|
||||
for binding in bindings {
|
||||
// Parse the shortcut, handling errors gracefully
|
||||
match binding.current_binding.parse::<Shortcut>() {
|
||||
Ok(shortcut) => {
|
||||
// Try to register the shortcut
|
||||
match app.global_shortcut().on_shortcut(
|
||||
shortcut,
|
||||
move |handler_app, scut, event| {
|
||||
if scut == &shortcut {
|
||||
println!("Global Shortcut pressed! {}", scut.into_string());
|
||||
if event.state == ShortcutState::Pressed {
|
||||
transcribe_pressed(handler_app);
|
||||
} else if event.state == ShortcutState::Released {
|
||||
transcribe_released(handler_app);
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Ok(_) => {
|
||||
// Additional actions on successful registration
|
||||
println!(
|
||||
"Successfully registered shortcut: {}",
|
||||
binding.current_binding
|
||||
);
|
||||
fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
||||
|
||||
active_bindings.insert(binding.id, binding.current_binding);
|
||||
}
|
||||
Err(err) => {
|
||||
// Log registration error
|
||||
eprintln!(
|
||||
"Failed to register shortcut {}: {}",
|
||||
binding.current_binding, err
|
||||
);
|
||||
}
|
||||
app.global_shortcut()
|
||||
.on_shortcut(shortcut, move |handler_app, scut, event| {
|
||||
if scut == &shortcut {
|
||||
println!("Global Shortcut pressed! {}", scut.into_string());
|
||||
if event.state == ShortcutState::Pressed {
|
||||
transcribe_pressed(handler_app);
|
||||
} else if event.state == ShortcutState::Released {
|
||||
transcribe_released(handler_app);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Log parsing error
|
||||
eprintln!(
|
||||
"Failed to parse shortcut {}: {}",
|
||||
binding.current_binding, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("couldnt register shortcut");
|
||||
}
|
||||
|
||||
fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
||||
|
||||
app.global_shortcut()
|
||||
.unregister(shortcut)
|
||||
.expect("couldnt unregister shortcut");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { load } from "@tauri-apps/plugin-store";
|
||||
import { SettingsSchema, ShortcutBinding } from "../../lib/types";
|
||||
import {
|
||||
SettingsSchema,
|
||||
ShortcutBinding,
|
||||
ShortcutBindingSchema,
|
||||
ShortcutBindingsMap,
|
||||
} from "../../lib/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export const KeyboardShortcuts: React.FC = () => {
|
||||
const [bindings, setBindings] = React.useState<ShortcutBinding[]>([]);
|
||||
const [bindings, setBindings] = React.useState<ShortcutBindingsMap>({});
|
||||
|
||||
useEffect(() => {
|
||||
load("settings_store.json", { autoSave: false }).then((r) => {
|
||||
|
|
@ -14,13 +20,22 @@ export const KeyboardShortcuts: React.FC = () => {
|
|||
setBindings(settings.bindings);
|
||||
});
|
||||
});
|
||||
|
||||
// setTimeout(() => {
|
||||
// console.log("invoked set binding");
|
||||
// invoke("change_binding", { id: "test", binding: "alt+d" }).then((b) => {
|
||||
// const newBinding = ShortcutBindingSchema.parse(b);
|
||||
// console.log(bindings);
|
||||
// setBindings((prev) => ({ ...prev, [newBinding.id]: newBinding }));
|
||||
// });
|
||||
// }, 1000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{bindings.map((binding) => (
|
||||
{Object.entries(bindings).map(([id, binding]) => (
|
||||
<div
|
||||
key={binding.id}
|
||||
key={id}
|
||||
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50"
|
||||
>
|
||||
<div>
|
||||
|
|
@ -30,13 +45,20 @@ export const KeyboardShortcuts: React.FC = () => {
|
|||
<p className="text-sm text-gray-500">{binding.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<React.Fragment>
|
||||
{/* <kbd className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded"> */}
|
||||
<div className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
|
||||
{binding.current_binding}
|
||||
</div>
|
||||
{/* </kbd> */}
|
||||
</React.Fragment>
|
||||
<div className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
|
||||
{binding.current_binding}
|
||||
</div>
|
||||
<button
|
||||
className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
invoke("reset_binding", { id }).then((b) => {
|
||||
const newBinding = ShortcutBindingSchema.parse(b);
|
||||
setBindings({ ...bindings, [newBinding.id]: newBinding });
|
||||
});
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,15 @@ export const ShortcutBindingSchema = z.object({
|
|||
current_binding: z.string(),
|
||||
});
|
||||
|
||||
export const ShortcutBindingsMapSchema = z.record(
|
||||
z.string(),
|
||||
ShortcutBindingSchema
|
||||
);
|
||||
|
||||
export const SettingsSchema = z.object({
|
||||
bindings: z.array(ShortcutBindingSchema),
|
||||
bindings: ShortcutBindingsMapSchema,
|
||||
});
|
||||
|
||||
export type ShortcutBinding = z.infer<typeof ShortcutBindingSchema>;
|
||||
export type ShortcutBindingsMap = z.infer<typeof ShortcutBindingsMapSchema>;
|
||||
export type Settings = z.infer<typeof SettingsSchema>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue