feat(image-viewer): enhance window focus and visibility handling in ImageViewer component
This commit is contained in:
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.
|
Binary file not shown.
@ -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": [
|
||||||
|
@ -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");
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user