Compare commits
16 Commits
e507fbc292
...
9407f54677
Author | SHA1 | Date | |
---|---|---|---|
9407f54677 | |||
2bcf06f5c6 | |||
ecd59ec814 | |||
0ef20264c1 | |||
0c2c8bde74 | |||
bb4760036e | |||
3e8df1ba6f | |||
1c265d8a60 | |||
4f2b78b9f1 | |||
d935d6a8b9 | |||
03656cf42e | |||
e735aca168 | |||
8ea0d53af7 | |||
8850b00595 | |||
e0fadb2c66 | |||
c3fc915e60 |
38
.cursor/rules/frontend-rules.mdc
Normal file
38
.cursor/rules/frontend-rules.mdc
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
You are an expert AI programming assistant focused on producing clean, readable TypeScript and Rust code for modern cross-platform desktop apps.
|
||||||
|
|
||||||
|
Use these rules for any code under /frontend folder.
|
||||||
|
|
||||||
|
You always use the latest versions of Tauri, Rust, SolidJS, and you're fluent in their latest features, best practices, and patterns.
|
||||||
|
|
||||||
|
You give accurate, thoughtful answers and think like a real dev—step-by-step.
|
||||||
|
|
||||||
|
Follow the user’s specs exactly. If a specs folder exists, check it before coding.
|
||||||
|
|
||||||
|
Begin with a detailed pseudo-code plan and confirm it with the user before writing actual code.
|
||||||
|
|
||||||
|
Write correct, complete, idiomatic, secure, performant, and bug-free code.
|
||||||
|
|
||||||
|
Prioritize readability unless performance is explicitly required.
|
||||||
|
|
||||||
|
Fully implement all requested features—no TODOs, stubs, or placeholders.
|
||||||
|
|
||||||
|
Use TypeScript's type system thoroughly for clarity and safety.
|
||||||
|
|
||||||
|
Style with TailwindCSS using utility-first principles.
|
||||||
|
|
||||||
|
Use Kobalte components effectively, building with Solid’s reactive model in mind.
|
||||||
|
|
||||||
|
Offload performance-heavy logic to Rust and ensure smooth integration with Tauri.
|
||||||
|
|
||||||
|
Guarantee tight coordination between SolidJS, Tauri, and Rust for a polished desktop UX.
|
||||||
|
|
||||||
|
When needed, provide bash scripts to generate config files or folder structures.
|
||||||
|
|
||||||
|
Be concise—cut the fluff.
|
||||||
|
|
||||||
|
If there's no solid answer, say so. If you're unsure, don't guess—own it.
|
53
frontend/.idea/workspace.xml
generated
Normal file
53
frontend/.idea/workspace.xml
generated
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="4ea94c05-c21c-40f9-ad16-43233a3011ee" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 5
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2w23zazSC8gW9XDwUxbl8Fam8DV" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"com.google.services.firebase.aqiPopupShown": "true",
|
||||||
|
"git-widget-placeholder": "feat/android-version",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "/home/johnc/Code/haystack-app/frontend",
|
||||||
|
"settings.editor.selected.configurable": "AndroidSdkUpdater"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="4ea94c05-c21c-40f9-ad16-43233a3011ee" name="Changes" comment="" />
|
||||||
|
<created>1745226104717</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1745226104717</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
Binary file not shown.
3
frontend/src-tauri/Cargo.lock
generated
3
frontend/src-tauri/Cargo.lock
generated
@ -7,6 +7,7 @@ name = "Haystack"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
|
"chrono",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"notify",
|
"notify",
|
||||||
"serde",
|
"serde",
|
||||||
@ -538,8 +539,10 @@ checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@ base64 = "0.21.7"
|
|||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
tauri-plugin-store = "2.0.0-beta.12"
|
tauri-plugin-store = "2.0.0-beta.12"
|
||||||
tauri-plugin-http = "2.0.0-beta.12"
|
tauri-plugin-http = "2.0.0-beta.12"
|
||||||
|
chrono = "0.4"
|
||||||
|
tauri-plugin-log = "2"
|
||||||
|
tauri-plugin-sharetarget = "0.1.6"
|
||||||
tauri-plugin-fs = "2"
|
tauri-plugin-fs = "2"
|
||||||
tauri-plugin-dialog = "2.2.1"
|
tauri-plugin-dialog = "2.2.1"
|
||||||
tauri-plugin-opener = "2.2.6"
|
tauri-plugin-opener = "2.2.6"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
|
pub mod screenshot;
|
||||||
|
pub mod shortcut;
|
||||||
mod state;
|
mod state;
|
||||||
mod utils;
|
pub mod utils;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
||||||
@ -17,14 +19,18 @@ pub fn desktop() {
|
|||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_store::Builder::new().build())
|
.plugin(tauri_plugin_store::Builder::new().build())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_sharetarget::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
// .plugin(tauri_plugin_dialog::init())
|
||||||
|
// .plugin(tauri_plugin_opener::init())
|
||||||
.manage(watcher_state)
|
.manage(watcher_state)
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::handle_selected_folder,
|
commands::handle_selected_folder,
|
||||||
shortcut::change_shortcut,
|
shortcut::change_shortcut,
|
||||||
shortcut::unregister_shortcut,
|
shortcut::unregister_shortcut,
|
||||||
shortcut::get_current_shortcut,
|
shortcut::get_current_shortcut,
|
||||||
|
shortcut::change_screenshot_shortcut,
|
||||||
|
shortcut::unregister_screenshot_shortcut,
|
||||||
|
shortcut::get_current_screenshot_shortcut,
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
setup_window(app)?;
|
setup_window(app)?;
|
||||||
|
46
frontend/src-tauri/src/screenshot.rs
Normal file
46
frontend/src-tauri/src/screenshot.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
use tauri::{AppHandle, Emitter, Runtime};
|
||||||
|
|
||||||
|
/// Takes a screenshot of a selected area and returns the image data as base64
|
||||||
|
pub fn take_area_screenshot<R: Runtime>(app: &AppHandle<R>) -> Result<String, String> {
|
||||||
|
// Create a temporary file path
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
|
||||||
|
let temp_file = temp_dir.join(format!("haystack_screenshot_{}.png", timestamp));
|
||||||
|
|
||||||
|
// Use screencapture command with -i flag for interactive selection
|
||||||
|
let output = Command::new("screencapture")
|
||||||
|
.arg("-i") // interactive selection
|
||||||
|
.arg("-x") // don't play sound
|
||||||
|
.arg("-o") // don't show cursor
|
||||||
|
.arg("-r") // don't add shadow
|
||||||
|
.arg(temp_file.to_str().unwrap())
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to execute screencapture: {}", e))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"screencapture failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the captured image
|
||||||
|
let contents =
|
||||||
|
fs::read(&temp_file).map_err(|e| format!("Failed to read screenshot file: {}", e))?;
|
||||||
|
|
||||||
|
// Convert to base64
|
||||||
|
let base64_string = BASE64.encode(&contents);
|
||||||
|
|
||||||
|
// Clean up the temporary file
|
||||||
|
if let Err(e) = fs::remove_file(&temp_file) {
|
||||||
|
println!("Warning: Failed to remove temporary screenshot file: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.emit("png-processed", base64_string.clone())
|
||||||
|
.map_err(|e| format!("Failed to emit event: {}", e))?;
|
||||||
|
|
||||||
|
Ok(base64_string)
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::screenshot::take_area_screenshot;
|
||||||
use tauri::App;
|
use tauri::App;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
@ -9,28 +10,36 @@ use tauri_plugin_global_shortcut::ShortcutState;
|
|||||||
use tauri_plugin_store::JsonValue;
|
use tauri_plugin_store::JsonValue;
|
||||||
use tauri_plugin_store::StoreExt;
|
use tauri_plugin_store::StoreExt;
|
||||||
|
|
||||||
/// Name of the Tauri storage
|
/// Constants for Tauri store configuration
|
||||||
const HAYSTACK_TAURI_STORE: &str = "haystack_tauri_store";
|
const HAYSTACK_TAURI_STORE: &str = "haystack_tauri_store"; // Name of the persistent store
|
||||||
|
const HAYSTACK_GLOBAL_SHORTCUT: &str = "haystack_global_shortcut"; // Key for storing the toggle window shortcut
|
||||||
|
const HAYSTACK_SCREENSHOT_SHORTCUT: &str = "haystack_screenshot_shortcut"; // Key for storing the screenshot shortcut
|
||||||
|
|
||||||
/// Key for storing global shortcuts
|
/// Platform-specific default shortcuts
|
||||||
const HAYSTACK_GLOBAL_SHORTCUT: &str = "haystack_global_shortcut";
|
|
||||||
|
|
||||||
/// Default shortcut for macOS
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const DEFAULT_SHORTCUT: &str = "command+shift+k";
|
const DEFAULT_SHORTCUT: &str = "command+shift+k"; // macOS uses Command key
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const DEFAULT_SCREENSHOT_SHORTCUT: &str = "command+shift+p"; // macOS screenshot shortcut
|
||||||
|
|
||||||
/// Default shortcut for Windows and Linux
|
|
||||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
const DEFAULT_SHORTCUT: &str = "ctrl+shift+k";
|
const DEFAULT_SHORTCUT: &str = "ctrl+shift+k"; // Windows/Linux use Ctrl key
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
|
const DEFAULT_SCREENSHOT_SHORTCUT: &str = "ctrl+shift+p"; // Windows/Linux screenshot shortcut
|
||||||
|
|
||||||
/// Set shortcut during application startup
|
/// Initializes the global shortcut during application startup.
|
||||||
|
/// This function:
|
||||||
|
/// 1. Checks if a shortcut is already stored
|
||||||
|
/// 2. Uses the stored shortcut if it exists
|
||||||
|
/// 3. Falls back to the platform-specific default if no shortcut is stored
|
||||||
|
/// 4. Registers the shortcut with the system
|
||||||
pub fn enable_shortcut(app: &App) {
|
pub fn enable_shortcut(app: &App) {
|
||||||
|
// Get or create the persistent store
|
||||||
let store = app
|
let store = app
|
||||||
.store(HAYSTACK_TAURI_STORE)
|
.store(HAYSTACK_TAURI_STORE)
|
||||||
.expect("Creating the store should not fail");
|
.expect("Creating the store should not fail");
|
||||||
|
|
||||||
// Use stored shortcut if it exists
|
// Initialize toggle window shortcut
|
||||||
if let Some(stored_shortcut) = store.get(HAYSTACK_GLOBAL_SHORTCUT) {
|
let toggle_shortcut = if let Some(stored_shortcut) = store.get(HAYSTACK_GLOBAL_SHORTCUT) {
|
||||||
let stored_shortcut_str = match stored_shortcut {
|
let stored_shortcut_str = match stored_shortcut {
|
||||||
JsonValue::String(str) => str,
|
JsonValue::String(str) => str,
|
||||||
unexpected_type => panic!(
|
unexpected_type => panic!(
|
||||||
@ -38,45 +47,72 @@ pub fn enable_shortcut(app: &App) {
|
|||||||
unexpected_type
|
unexpected_type
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let stored_shortcut = stored_shortcut_str
|
stored_shortcut_str
|
||||||
.parse::<Shortcut>()
|
.parse::<Shortcut>()
|
||||||
.expect("Stored shortcut string should be valid");
|
.expect("Stored shortcut string should be valid")
|
||||||
_register_shortcut_upon_start(app, stored_shortcut); // Register stored shortcut
|
|
||||||
} else {
|
} else {
|
||||||
// Use default shortcut if none is stored
|
|
||||||
store.set(
|
store.set(
|
||||||
HAYSTACK_GLOBAL_SHORTCUT,
|
HAYSTACK_GLOBAL_SHORTCUT,
|
||||||
JsonValue::String(DEFAULT_SHORTCUT.to_string()),
|
JsonValue::String(DEFAULT_SHORTCUT.to_string()),
|
||||||
);
|
);
|
||||||
let default_shortcut = DEFAULT_SHORTCUT
|
DEFAULT_SHORTCUT
|
||||||
.parse::<Shortcut>()
|
.parse::<Shortcut>()
|
||||||
.expect("Default shortcut should be valid");
|
.expect("Default shortcut should be valid")
|
||||||
_register_shortcut_upon_start(app, default_shortcut); // Register default shortcut
|
};
|
||||||
}
|
|
||||||
|
// Initialize screenshot shortcut
|
||||||
|
let screenshot_shortcut = if let Some(stored_shortcut) = store.get(HAYSTACK_SCREENSHOT_SHORTCUT)
|
||||||
|
{
|
||||||
|
let stored_shortcut_str = match stored_shortcut {
|
||||||
|
JsonValue::String(str) => str,
|
||||||
|
unexpected_type => panic!(
|
||||||
|
"Haystack shortcuts should be stored as strings, found type: {} ",
|
||||||
|
unexpected_type
|
||||||
|
),
|
||||||
|
};
|
||||||
|
stored_shortcut_str
|
||||||
|
.parse::<Shortcut>()
|
||||||
|
.expect("Stored shortcut string should be valid")
|
||||||
|
} else {
|
||||||
|
store.set(
|
||||||
|
HAYSTACK_SCREENSHOT_SHORTCUT,
|
||||||
|
JsonValue::String(DEFAULT_SCREENSHOT_SHORTCUT.to_string()),
|
||||||
|
);
|
||||||
|
DEFAULT_SCREENSHOT_SHORTCUT
|
||||||
|
.parse::<Shortcut>()
|
||||||
|
.expect("Default screenshot shortcut should be valid")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register both shortcuts
|
||||||
|
register_shortcut_upon_start(app, toggle_shortcut, screenshot_shortcut);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current stored shortcut as a string
|
/// Returns the currently configured shortcut as a string.
|
||||||
|
/// This is exposed as a Tauri command for the frontend to query.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_current_shortcut<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
|
pub fn get_current_shortcut<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
|
||||||
let shortcut = _get_shortcut(&app);
|
Ok(get_shortcut_from_store(&app))
|
||||||
Ok(shortcut)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregister the current shortcut in Tauri
|
/// Unregisters the current global shortcut from the system.
|
||||||
|
/// This is exposed as a Tauri command for the frontend to trigger.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn unregister_shortcut<R: Runtime>(app: AppHandle<R>) {
|
pub fn unregister_shortcut<R: Runtime>(app: AppHandle<R>) {
|
||||||
let shortcut_str = _get_shortcut(&app);
|
let shortcut_str = get_shortcut_from_store(&app);
|
||||||
let shortcut = shortcut_str
|
let shortcut = shortcut_str
|
||||||
.parse::<Shortcut>()
|
.parse::<Shortcut>()
|
||||||
.expect("Stored shortcut string should be valid");
|
.expect("Stored shortcut string should be valid");
|
||||||
|
|
||||||
// Unregister the shortcut
|
|
||||||
app.global_shortcut()
|
app.global_shortcut()
|
||||||
.unregister(shortcut)
|
.unregister(shortcut)
|
||||||
.expect("Failed to unregister shortcut")
|
.expect("Failed to unregister shortcut")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the global shortcut
|
/// Changes the global shortcut to a new key combination.
|
||||||
|
/// This function:
|
||||||
|
/// 1. Validates the new shortcut
|
||||||
|
/// 2. Stores it in the persistent store
|
||||||
|
/// 3. Registers it with the system
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn change_shortcut<R: Runtime>(
|
pub fn change_shortcut<R: Runtime>(
|
||||||
app: AppHandle<R>,
|
app: AppHandle<R>,
|
||||||
@ -96,28 +132,40 @@ pub fn change_shortcut<R: Runtime>(
|
|||||||
store.set(HAYSTACK_GLOBAL_SHORTCUT, JsonValue::String(key));
|
store.set(HAYSTACK_GLOBAL_SHORTCUT, JsonValue::String(key));
|
||||||
|
|
||||||
// Register the new shortcut
|
// Register the new shortcut
|
||||||
_register_shortcut(&app, shortcut);
|
register_shortcut(&app, shortcut);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to register a shortcut, primarily for updating shortcuts
|
/// Handles the window visibility toggle logic when the shortcut is pressed.
|
||||||
fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
|
/// This function:
|
||||||
|
/// 1. Hides the window if it's visible
|
||||||
|
/// 2. Shows and focuses the window if it's hidden
|
||||||
|
/// 3. Emits a 'focus-search' event when showing the window
|
||||||
|
fn handle_window_visibility<R: Runtime>(app: &AppHandle<R>, window: &tauri::WebviewWindow<R>) {
|
||||||
|
if window.is_visible().unwrap() {
|
||||||
|
window.hide().unwrap();
|
||||||
|
} else {
|
||||||
|
window.show().unwrap();
|
||||||
|
window.set_focus().unwrap();
|
||||||
|
app.emit("focus-search", ()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a new shortcut with the system.
|
||||||
|
/// This is used when changing shortcuts during runtime.
|
||||||
|
/// The function:
|
||||||
|
/// 1. Gets the main window
|
||||||
|
/// 2. Sets up the shortcut handler
|
||||||
|
/// 3. Registers the shortcut with the system
|
||||||
|
fn register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
|
||||||
let main_window = app.get_webview_window("main").unwrap();
|
let main_window = app.get_webview_window("main").unwrap();
|
||||||
// Register global shortcut and define its behavior
|
|
||||||
app.global_shortcut()
|
app.global_shortcut()
|
||||||
.on_shortcut(shortcut, move |app, scut, event| {
|
.on_shortcut(shortcut, move |app, scut, event| {
|
||||||
if scut == &shortcut {
|
if scut == &shortcut {
|
||||||
if let ShortcutState::Pressed = event.state() {
|
if let ShortcutState::Pressed = event.state() {
|
||||||
// Toggle window visibility
|
handle_window_visibility(app, &main_window);
|
||||||
if main_window.is_visible().unwrap() {
|
|
||||||
main_window.hide().unwrap(); // Hide window
|
|
||||||
} else {
|
|
||||||
main_window.show().unwrap(); // Show window
|
|
||||||
main_window.set_focus().unwrap(); // Focus window
|
|
||||||
// Emit focus-search event
|
|
||||||
app.emit("focus-search", ()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -125,39 +173,49 @@ fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to register shortcuts during application startup
|
/// Registers a shortcut during application startup.
|
||||||
fn _register_shortcut_upon_start(app: &App, shortcut: Shortcut) {
|
/// This is similar to register_shortcut but handles the initial plugin setup.
|
||||||
|
/// The function:
|
||||||
|
/// 1. Gets the main window
|
||||||
|
/// 2. Sets up the global shortcut plugin
|
||||||
|
/// 3. Registers the shortcut with the system
|
||||||
|
fn register_shortcut_upon_start(
|
||||||
|
app: &App,
|
||||||
|
toggle_shortcut: Shortcut,
|
||||||
|
screenshot_shortcut: Shortcut,
|
||||||
|
) {
|
||||||
let window = app
|
let window = app
|
||||||
.get_webview_window("main")
|
.get_webview_window("main")
|
||||||
.expect("webview to be defined");
|
.expect("webview to be defined");
|
||||||
|
|
||||||
// Initialize global shortcut and set its handler
|
|
||||||
app.handle()
|
app.handle()
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_global_shortcut::Builder::new()
|
tauri_plugin_global_shortcut::Builder::new()
|
||||||
.with_handler(move |app, scut, event| {
|
.with_handler(move |app, scut, event| {
|
||||||
if scut == &shortcut {
|
if scut == &toggle_shortcut {
|
||||||
if let ShortcutState::Pressed = event.state() {
|
if let ShortcutState::Pressed = event.state() {
|
||||||
// Toggle window visibility
|
handle_window_visibility(app, &window);
|
||||||
if window.is_visible().unwrap() {
|
}
|
||||||
window.hide().unwrap(); // Hide window
|
} else if scut == &screenshot_shortcut {
|
||||||
} else {
|
if let ShortcutState::Pressed = event.state() {
|
||||||
window.show().unwrap(); // Show window
|
// TODO: Implement screenshot functionality
|
||||||
window.set_focus().unwrap(); // Focus window
|
println!("Screenshot shortcut pressed");
|
||||||
// Emit focus-search event
|
}
|
||||||
app.emit("focus-search", ()).unwrap();
|
if let Err(e) = take_area_screenshot(app) {
|
||||||
}
|
println!("Failed to take screenshot: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
app.global_shortcut().register(shortcut).unwrap(); // Register global shortcut
|
app.global_shortcut().register(toggle_shortcut).unwrap();
|
||||||
|
app.global_shortcut().register(screenshot_shortcut).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the stored global shortcut as a string
|
/// Retrieves the currently stored shortcut from the persistent store.
|
||||||
pub fn _get_shortcut<R: Runtime>(app: &AppHandle<R>) -> String {
|
/// This is a helper function used by other functions to access the stored shortcut.
|
||||||
|
fn get_shortcut_from_store<R: Runtime>(app: &AppHandle<R>) -> String {
|
||||||
let store = app
|
let store = app
|
||||||
.get_store(HAYSTACK_TAURI_STORE)
|
.get_store(HAYSTACK_TAURI_STORE)
|
||||||
.expect("Store should already be loaded or created");
|
.expect("Store should already be loaded or created");
|
||||||
@ -173,3 +231,73 @@ pub fn _get_shortcut<R: Runtime>(app: &AppHandle<R>) -> String {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_current_screenshot_shortcut<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
|
||||||
|
Ok(get_screenshot_shortcut_from_store(&app))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn unregister_screenshot_shortcut<R: Runtime>(app: AppHandle<R>) {
|
||||||
|
let shortcut_str = get_screenshot_shortcut_from_store(&app);
|
||||||
|
let shortcut = shortcut_str
|
||||||
|
.parse::<Shortcut>()
|
||||||
|
.expect("Stored shortcut string should be valid");
|
||||||
|
|
||||||
|
app.global_shortcut()
|
||||||
|
.unregister(shortcut)
|
||||||
|
.expect("Failed to unregister screenshot shortcut")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn change_screenshot_shortcut<R: Runtime>(
|
||||||
|
app: AppHandle<R>,
|
||||||
|
_window: tauri::Window<R>,
|
||||||
|
key: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let shortcut = match key.parse::<Shortcut>() {
|
||||||
|
Ok(shortcut) => shortcut,
|
||||||
|
Err(_) => return Err(format!("Invalid screenshot shortcut {}", key)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let store = app
|
||||||
|
.get_store(HAYSTACK_TAURI_STORE)
|
||||||
|
.expect("Store should already be loaded or created");
|
||||||
|
store.set(HAYSTACK_SCREENSHOT_SHORTCUT, JsonValue::String(key));
|
||||||
|
|
||||||
|
register_screenshot_shortcut(&app, shortcut);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_screenshot_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
|
||||||
|
app.global_shortcut()
|
||||||
|
.on_shortcut(shortcut, move |app, scut, event| {
|
||||||
|
if scut == &shortcut {
|
||||||
|
if let ShortcutState::Pressed = event.state() {
|
||||||
|
if let Err(e) = take_area_screenshot(app) {
|
||||||
|
println!("Failed to take screenshot: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|err| format!("Failed to register new screenshot shortcut '{}'", err))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_screenshot_shortcut_from_store<R: Runtime>(app: &AppHandle<R>) -> String {
|
||||||
|
let store = app
|
||||||
|
.get_store(HAYSTACK_TAURI_STORE)
|
||||||
|
.expect("Store should already be loaded or created");
|
||||||
|
|
||||||
|
match store
|
||||||
|
.get(HAYSTACK_SCREENSHOT_SHORTCUT)
|
||||||
|
.expect("Screenshot shortcut should already be stored")
|
||||||
|
{
|
||||||
|
JsonValue::String(str) => str,
|
||||||
|
unexpected_type => panic!(
|
||||||
|
"Haystack shortcuts should be stored as strings, found type: {} ",
|
||||||
|
unexpected_type
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { createEffect } from "solid-js";
|
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { createEffect, createSignal } from "solid-js";
|
||||||
import { sendImage } from "../network";
|
import { sendImage } from "../network";
|
||||||
|
|
||||||
|
// TODO: This component should focus the window and show preview of screenshot,
|
||||||
|
// before we send it to backend, potentially we could draw and annotate
|
||||||
|
// OR we kill this and do stuff siltently
|
||||||
|
// anyhow keeping it like this for now
|
||||||
export function ImageViewer() {
|
export function ImageViewer() {
|
||||||
// const [latestImage, setLatestImage] = createSignal<string | null>(null);
|
// const [latestImage, setLatestImage] = createSignal<string | null>(null);
|
||||||
|
|
||||||
@ -11,10 +17,15 @@ export function ImageViewer() {
|
|||||||
console.log("Received processed PNG", event);
|
console.log("Received processed PNG", event);
|
||||||
const base64Data = event.payload as string;
|
const base64Data = event.payload as string;
|
||||||
|
|
||||||
|
const appWindow = getCurrentWindow();
|
||||||
|
|
||||||
|
appWindow.show();
|
||||||
|
appWindow.setFocus();
|
||||||
|
|
||||||
// setLatestImage(`data:image/png;base64,${base64Data}`);
|
// setLatestImage(`data:image/png;base64,${base64Data}`);
|
||||||
|
|
||||||
const result = await sendImage("test-image.png", base64Data);
|
const result = await sendImage("test-image.png", base64Data);
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
console.log("DBG: ", result);
|
console.log("DBG: ", result);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -26,9 +37,7 @@ export function ImageViewer() {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
// return (
|
// return (
|
||||||
// <div>
|
// <div class="fixed inset-0">
|
||||||
// <FolderPicker />
|
|
||||||
|
|
||||||
// {latestImage() && (
|
// {latestImage() && (
|
||||||
// <div class="mt-4">
|
// <div class="mt-4">
|
||||||
// <h3>Latest Processed Image:</h3>
|
// <h3>Latest Processed Image:</h3>
|
||||||
|
Reference in New Issue
Block a user