most basic working being able to change key bindings.
This commit is contained in:
parent
9b5f1c5a04
commit
b58b65dabc
5 changed files with 180 additions and 28 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -19,6 +19,7 @@
|
|||
"@tauri-apps/plugin-store": "~2",
|
||||
"@tauri-apps/plugin-stronghold": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"keycode": "^2.2.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwindcss": "^4.0.2",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::App;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Manager;
|
||||
|
|
@ -45,22 +46,54 @@ pub fn init_shortcuts(app: &App) {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct BindingResponse {
|
||||
success: bool,
|
||||
binding: Option<ShortcutBinding>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn change_binding(app: AppHandle, id: String, binding: String) -> ShortcutBinding {
|
||||
pub fn change_binding(
|
||||
app: AppHandle,
|
||||
id: String,
|
||||
binding: String,
|
||||
) -> Result<BindingResponse, String> {
|
||||
let mut settings = settings::get_settings(&app);
|
||||
|
||||
// Get the binding to modify
|
||||
let binding_to_modify = settings.bindings.get(&id).unwrap().clone();
|
||||
let binding_to_modify = match settings.bindings.get(&id) {
|
||||
Some(binding) => binding.clone(),
|
||||
None => {
|
||||
return Ok(BindingResponse {
|
||||
success: false,
|
||||
binding: None,
|
||||
error: Some(format!("Binding with id '{}' not found", id)),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Unregister the existing binding
|
||||
_unregister_shortcut(&app, binding_to_modify.clone());
|
||||
if let Err(e) = _unregister_shortcut(&app, binding_to_modify.clone()) {
|
||||
return Ok(BindingResponse {
|
||||
success: false,
|
||||
binding: None,
|
||||
error: Some(format!("Failed to unregister shortcut: {}", 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());
|
||||
if let Err(e) = _register_shortcut(&app, updated_binding.clone()) {
|
||||
return Ok(BindingResponse {
|
||||
success: false,
|
||||
binding: None,
|
||||
error: Some(format!("Failed to register shortcut: {}", e)),
|
||||
});
|
||||
}
|
||||
|
||||
// Update the binding in the settings
|
||||
settings.bindings.insert(id, updated_binding.clone());
|
||||
|
|
@ -69,17 +102,26 @@ pub fn change_binding(app: AppHandle, id: String, binding: String) -> ShortcutBi
|
|||
settings::write_settings(&app, settings);
|
||||
|
||||
// Return the updated binding
|
||||
updated_binding
|
||||
Ok(BindingResponse {
|
||||
success: true,
|
||||
binding: Some(updated_binding),
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn reset_binding(app: AppHandle, id: String) -> ShortcutBinding {
|
||||
pub fn reset_binding(app: AppHandle, id: String) -> Result<BindingResponse, String> {
|
||||
let binding = settings::get_stored_binding(&app, &id);
|
||||
|
||||
return change_binding(app, id, binding.default_binding);
|
||||
}
|
||||
|
||||
fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
||||
fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
||||
// Parse shortcut and return error if it fails
|
||||
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(format!("Failed to parse shortcut: {}", e)),
|
||||
};
|
||||
|
||||
app.global_shortcut()
|
||||
.on_shortcut(shortcut, move |handler_app, scut, event| {
|
||||
|
|
@ -92,13 +134,20 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
|||
}
|
||||
}
|
||||
})
|
||||
.expect("couldnt register shortcut");
|
||||
.map_err(|e| format!("Couldn't register shortcut: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
||||
fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
||||
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(format!("Failed to parse shortcut: {}", e)),
|
||||
};
|
||||
|
||||
app.global_shortcut()
|
||||
.unregister(shortcut)
|
||||
.expect("couldnt unregister shortcut");
|
||||
.map_err(|e| format!("Failed to unregister shortcut: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { load } from "@tauri-apps/plugin-store";
|
||||
import {
|
||||
BindingResponseSchema,
|
||||
SettingsSchema,
|
||||
ShortcutBinding,
|
||||
ShortcutBindingSchema,
|
||||
ShortcutBindingsMap,
|
||||
} from "../../lib/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import keycode from "keycode";
|
||||
|
||||
export const KeyboardShortcuts: React.FC = () => {
|
||||
const [bindings, setBindings] = React.useState<ShortcutBindingsMap>({});
|
||||
const [keyPressed, setKeyPressed] = useState<string[]>([]);
|
||||
const [recordedKeys, setRecordedKeys] = useState<string[]>([]);
|
||||
const [editingShortcutId, setEditingShortcutId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
load("settings_store.json", { autoSave: false }).then((r) => {
|
||||
|
|
@ -20,17 +26,88 @@ 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);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Only add event listeners when we're in editing mode
|
||||
if (editingShortcutId === null) return;
|
||||
|
||||
console.log("keyPressed", keyPressed);
|
||||
|
||||
// Keyboard event listeners
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const key = keycode(e).toLowerCase();
|
||||
console.log("You pressed", key);
|
||||
|
||||
if (!keyPressed.includes(key)) {
|
||||
setKeyPressed((prev) => [...prev, key]);
|
||||
// Also add to recorded keys if not already there
|
||||
if (!recordedKeys.includes(key)) {
|
||||
setRecordedKeys((prev) => [...prev, key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const key = keycode(e).toLowerCase();
|
||||
|
||||
// Remove from currently pressed keys
|
||||
setKeyPressed((prev) => prev.filter((k) => k !== key));
|
||||
|
||||
// If no keys are pressed anymore, commit the shortcut
|
||||
if (keyPressed.length === 1 && keyPressed[0] === key) {
|
||||
// Create the shortcut string from all recorded keys
|
||||
const newShortcut = recordedKeys.join("+");
|
||||
|
||||
if (editingShortcutId && bindings[editingShortcutId]) {
|
||||
const updatedBinding = {
|
||||
...bindings[editingShortcutId],
|
||||
current_binding: newShortcut,
|
||||
};
|
||||
|
||||
setBindings((prev) => ({
|
||||
...prev,
|
||||
[editingShortcutId]: updatedBinding,
|
||||
}));
|
||||
|
||||
invoke("change_binding", {
|
||||
id: editingShortcutId,
|
||||
binding: newShortcut,
|
||||
});
|
||||
|
||||
// Exit editing mode and reset states
|
||||
setEditingShortcutId(null);
|
||||
setKeyPressed([]);
|
||||
setRecordedKeys([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
};
|
||||
}, [keyPressed, recordedKeys, editingShortcutId, bindings]);
|
||||
|
||||
// Start recording a new shortcut
|
||||
const startRecording = (id: string) => {
|
||||
setEditingShortcutId(id);
|
||||
setKeyPressed([]);
|
||||
setRecordedKeys([]);
|
||||
};
|
||||
|
||||
// Format the current shortcut keys being recorded
|
||||
const formatCurrentKeys = () => {
|
||||
return recordedKeys.length > 0 ? recordedKeys.join("+") : "Press keys...";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(bindings).map(([id, binding]) => (
|
||||
|
|
@ -45,15 +122,33 @@ export const KeyboardShortcuts: React.FC = () => {
|
|||
<p className="text-sm text-gray-500">{binding.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<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>
|
||||
{editingShortcutId === id ? (
|
||||
<div className="px-2 py-1 text-sm font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded min-w-[100px] text-center">
|
||||
{formatCurrentKeys()}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded cursor-pointer hover:bg-gray-200"
|
||||
onClick={() => startRecording(id)}
|
||||
>
|
||||
{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 });
|
||||
console.log("reset");
|
||||
const newBinding = BindingResponseSchema.parse(b);
|
||||
|
||||
if (!newBinding.success) {
|
||||
console.error("Error resetting binding:", newBinding.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const binding = newBinding.binding!;
|
||||
|
||||
setBindings({ ...bindings, [binding.id]: binding });
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ export const SettingsSchema = z.object({
|
|||
bindings: ShortcutBindingsMapSchema,
|
||||
});
|
||||
|
||||
export const BindingResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
binding: ShortcutBindingSchema.nullable(),
|
||||
error: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type BindingResponse = z.infer<typeof BindingResponseSchema>;
|
||||
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