feat(image-viewer): enhance window focus and visibility handling in ImageViewer component

This commit is contained in:
2025-04-22 20:56:08 +02:00
parent c3f4403145
commit 2b022c31cb
6 changed files with 82 additions and 41 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View 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 users 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 Solids 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.

Binary file not shown.

View File

@ -13,6 +13,8 @@
"global-shortcut:allow-unregister", "global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all", "global-shortcut:allow-unregister-all",
"http:default", "http:default",
"core:window:allow-show",
"core:window:allow-set-focus",
{ {
"identifier": "http:default", "identifier": "http:default",
"allow": [ "allow": [

View File

@ -29,8 +29,7 @@ pub fn enable_shortcut(app: &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 let shortcut = if let Some(stored_shortcut) = store.get(HAYSTACK_GLOBAL_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,39 +37,36 @@ 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 };
}
register_shortcut_upon_start(app, shortcut);
} }
/// Get the current stored shortcut as a string /// Get the current stored shortcut as a string
#[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 /// Unregister the current shortcut in Tauri
#[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")
@ -96,28 +92,31 @@ 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 handle window visibility toggle
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();
}
}
/// Helper function to register a shortcut, primarily for updating shortcuts /// Helper function to register a shortcut, primarily for updating shortcuts
fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) { 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();
}
} }
} }
}) })
@ -126,38 +125,29 @@ fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
} }
/// Helper function to register shortcuts during application startup /// Helper function to register shortcuts during application startup
fn _register_shortcut_upon_start(app: &App, shortcut: Shortcut) { fn register_shortcut_upon_start(app: &App, 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 == &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 {
window.show().unwrap(); // Show window
window.set_focus().unwrap(); // Focus window
// Emit focus-search event
app.emit("focus-search", ()).unwrap();
}
} }
} }
}) })
.build(), .build(),
) )
.unwrap(); .unwrap();
app.global_shortcut().register(shortcut).unwrap(); // Register global shortcut app.global_shortcut().register(shortcut).unwrap();
} }
/// Retrieve the stored global shortcut as a string /// Retrieve the stored global shortcut as a string
pub fn _get_shortcut<R: Runtime>(app: &AppHandle<R>) -> String { 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");

View File

@ -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);
}); });