diff --git a/package-lock.json b/package-lock.json index faf21ca..28542fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "snipp-app", "version": "0.1.0", "dependencies": { + "@crabnebula/tauri-plugin-drag": "^2.1.0", "@radix-ui/react-slot": "^1.2.3", "@tauri-apps/api": "^2.8.0", "@tauri-apps/plugin-global-shortcut": "^2.3.0", @@ -578,6 +579,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@crabnebula/tauri-plugin-drag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@crabnebula/tauri-plugin-drag/-/tauri-plugin-drag-2.1.0.tgz", + "integrity": "sha512-LnUXAZwQt1cdMoGDLJ6ogW9wFCYServCZXlGadS7CA+CZ9eXS7L+Q7QyQW6g/zGw9YI2MwKFqf1aSNBGyWw+OA==", + "dependencies": { + "@tauri-apps/api": "^2.0.0" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -5868,9 +5877,9 @@ "license": "MIT" }, "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", "license": "MIT", "optional": true, "dependencies": { @@ -5881,9 +5890,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "optional": true, "bin": { diff --git a/package.json b/package.json index 5c79a61..465b11d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "tailwindcss": "^3.4.18" }, "dependencies": { + "@crabnebula/tauri-plugin-drag": "^2.1.0", "@radix-ui/react-slot": "^1.2.3", "@tauri-apps/api": "^2.8.0", "@tauri-apps/plugin-global-shortcut": "^2.3.0", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4235fae..7b8f84a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -559,7 +559,7 @@ checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", - "cocoa-foundation", + "cocoa-foundation 0.1.2", "core-foundation 0.9.4", "core-graphics 0.23.2", "foreign-types", @@ -567,6 +567,22 @@ dependencies = [ "objc", ] +[[package]] +name = "cocoa" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" +dependencies = [ + "bitflags 2.9.4", + "block", + "cocoa-foundation 0.2.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + [[package]] name = "cocoa-foundation" version = "0.1.2" @@ -581,6 +597,19 @@ dependencies = [ "objc", ] +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.9.4", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -987,6 +1016,27 @@ dependencies = [ "serde", ] +[[package]] +name = "drag" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fd9ae1736d6ebb2e472740fbee86fb2178b8d56feb98a6751411d4c95b7e72" +dependencies = [ + "cocoa 0.26.1", + "core-graphics 0.24.0", + "dunce", + "gdk", + "gdkx11", + "gtk", + "log", + "objc", + "raw-window-handle", + "serde", + "thiserror 1.0.69", + "windows 0.52.0", + "windows-core 0.58.0", +] + [[package]] name = "dtoa" version = "1.0.10" @@ -3971,7 +4021,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.7", "chrono", - "cocoa", + "cocoa 0.25.0", "dirs 5.0.1", "image 0.24.9", "objc", @@ -3981,6 +4031,7 @@ dependencies = [ "tauri-build", "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", + "tauri-plugin-drag", "tauri-plugin-fs", "tauri-plugin-global-shortcut", "tauri-plugin-opener", @@ -4191,7 +4242,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -4263,7 +4314,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -4379,6 +4430,21 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-drag" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57788e3175d71de47f449e642ebf135e86a41279be1e4714c7eb8c682abb37ca" +dependencies = [ + "base64 0.21.7", + "drag", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + [[package]] name = "tauri-plugin-fs" version = "2.4.2" @@ -4434,7 +4500,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.17", "url", - "windows", + "windows 0.61.3", "zbus", ] @@ -4496,7 +4562,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -4522,7 +4588,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -5386,10 +5452,10 @@ checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-implement 0.60.1", + "windows-interface 0.59.2", ] [[package]] @@ -5410,7 +5476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.17", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -5466,6 +5532,18 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-implement 0.52.0", + "windows-interface 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -5488,14 +5566,36 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.1", + "windows-interface 0.59.2", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -5507,8 +5607,8 @@ version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.1", + "windows-interface 0.59.2", "windows-link 0.2.0", "windows-result 0.4.0", "windows-strings 0.5.0", @@ -5525,6 +5625,28 @@ dependencies = [ "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "windows-implement" version = "0.60.1" @@ -5536,6 +5658,28 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "windows-interface" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "windows-interface" version = "0.59.2" @@ -5569,6 +5713,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5587,6 +5740,16 @@ dependencies = [ "windows-link 0.2.0", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -6009,7 +6172,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 997e41e..c05f10a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,7 @@ base64 = "0.21" image = "0.24" chrono = { version = "0.4", features = ["serde"] } tauri-plugin-positioner = { version = "2.0.0", features = ["tray-icon"] } +tauri-plugin-drag = "2.1.0" [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 92b2fed..5dcaf77 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -15,9 +15,11 @@ "clipboard-manager:allow-read-image", "clipboard-manager:allow-write-image", "fs:allow-read-file", + "fs:scope-temp-recursive", "fs:allow-remove", "fs:allow-create", "fs:allow-write-file", - "dialog:allow-open" + "dialog:allow-open", + "drag:default" ] } diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index b8427b3..fe57307 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -196,9 +196,9 @@ mod tests { let config = AppConfig::default(); assert_eq!(config.default_save_location, "/test/home/Desktop"); - assert_eq!(config.capture_hotkey, "CommandOrControl+Shift+2"); + assert_eq!(config.capture_hotkey, "CommandOrControl+Shift+S"); assert_eq!(config.preferences_hotkey, "CommandOrControl+Comma"); - assert_eq!(config.auto_copy_after_capture, false); + assert_eq!(config.auto_copy_after_capture, true); assert_eq!(config.auto_copy_after_edit, false); match original_home { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c11b3df..ac52d81 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -287,9 +287,8 @@ async fn show_popup_window(app_handle: &AppHandle, screenshot_data: &ScreenshotD tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; } - // Calculate bottom-left position with padding - let popup_width = 280.0; - let popup_height = 200.0; + let popup_width = 320.0; + let popup_height = 220.0; let padding = 20.0; // Get primary monitor size for proper positioning @@ -319,6 +318,7 @@ async fn show_popup_window(app_handle: &AppHandle, screenshot_data: &ScreenshotD .resizable(false) .minimizable(false) .maximizable(false) + .disable_drag_drop_handler() .build() .map_err(|e| format!("Failed to create popup window: {}", e))?; @@ -789,6 +789,34 @@ async fn copy_edited_screenshot( Ok(()) } +#[tauri::command] +async fn prepare_drag_file(timestamp: u64) -> Result { + let cache_key = timestamp.to_string(); + let image_data = { + let cache = SCREENSHOT_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let cache_guard = cache.lock().map_err(|e| format!("Cache lock error: {}", e))?; + cache_guard.get(&cache_key).cloned() + }; + + let image_data = image_data.ok_or("Screenshot data not found in memory cache")?; + + let temp_path = std::env::temp_dir().join(build_screenshot_filename(timestamp)); + + std::fs::write(&temp_path, &image_data) + .map_err(|e| format!("Failed to write drag temp file: {}", e))?; + + Ok(temp_path.to_string_lossy().to_string()) +} + +#[tauri::command] +async fn cleanup_drag_file(timestamp: u64) -> Result<(), String> { + let temp_path = std::env::temp_dir().join(build_screenshot_filename(timestamp)); + if temp_path.exists() { + let _ = std::fs::remove_file(&temp_path); + } + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] fn setup_global_shortcuts(app: &App) -> Result<(), Box> { app.handle() @@ -873,6 +901,7 @@ pub fn run() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_positioner::init()) + .plugin(tauri_plugin_drag::init()) .manage(ConfigState::new(config_manager)) .manage(HistoryState::new(history_manager)) .manage(ThumbnailState::new(thumbnail_generator)) @@ -908,6 +937,8 @@ pub fn run() { close_editor_window, save_edited_screenshot, copy_edited_screenshot, + prepare_drag_file, + cleanup_drag_file, hide_window, show_window ]) @@ -984,4 +1015,50 @@ mod tests { assert!(filename.ends_with(".png")); assert_eq!(filename.len(), 30); } + + #[test] + fn test_prepare_drag_file_writes_and_returns_path() { + let cache = SCREENSHOT_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let timestamp = 8888888888u64; + let test_png = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; + { + let mut guard = cache.lock().unwrap(); + guard.insert(timestamp.to_string(), test_png.clone()); + } + + let temp_path = std::env::temp_dir().join(build_screenshot_filename(timestamp)); + let data = { + let guard = cache.lock().unwrap(); + guard.get(×tamp.to_string()).cloned().unwrap() + }; + std::fs::write(&temp_path, &data).unwrap(); + + assert!(temp_path.exists()); + let read_back = std::fs::read(&temp_path).unwrap(); + assert_eq!(read_back, test_png); + + // Path should be absolute, not a file:// URL + let path_str = temp_path.to_string_lossy().to_string(); + assert!(path_str.starts_with('/')); + assert!(!path_str.starts_with("file://")); + assert!(path_str.ends_with(".png")); + + // Cleanup + std::fs::remove_file(&temp_path).unwrap(); + { + let mut guard = cache.lock().unwrap(); + guard.remove(×tamp.to_string()); + } + } + + #[test] + fn test_cleanup_drag_file_ignores_missing() { + let temp_path = std::env::temp_dir().join(build_screenshot_filename(2)); + let _ = std::fs::remove_file(&temp_path); + // Should not panic + if temp_path.exists() { + let _ = std::fs::remove_file(&temp_path); + } + assert!(!temp_path.exists()); + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5099906..c7ac89e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -4,6 +4,9 @@ "version": "0.1.0", "identifier": "com.ops.snipp", "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", "frontendDist": "../dist" }, "app": { @@ -21,8 +24,8 @@ { "label": "popup", "title": "Screenshot Preview", - "width": 1920, - "height": 1080, + "width": 320, + "height": 220, "decorations": false, "transparent": true, "alwaysOnTop": true, @@ -32,6 +35,7 @@ "minimizable": false, "maximizable": false, "closable": false, + "dragDropEnabled": false, "url": "popup.html" }, { @@ -49,7 +53,10 @@ ], "security": { "csp": null, - "capabilities": ["default", "toast"] + "capabilities": ["default", "toast"], + "assetProtocol": { + "scope": ["$TEMP/**", "/var/folders/**", "/tmp/**"] + } } }, "bundle": { diff --git a/src/components/ScreenshotPreview.tsx b/src/components/ScreenshotPreview.tsx index 8b7a0ce..aa54b28 100644 --- a/src/components/ScreenshotPreview.tsx +++ b/src/components/ScreenshotPreview.tsx @@ -1,10 +1,12 @@ -import { useRef, useState } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { Save, Copy, Trash2, X, Pencil } from 'lucide-react'; -import { cn } from '@/lib/utils'; +import { cn, debugLog } from '@/lib/utils'; import { useScreenshot } from '@/hooks/useScreenshot'; +import { startDrag } from '@crabnebula/tauri-plugin-drag'; interface ScreenshotPreviewProps { imageUrl: string; + dragFilePath?: string; onSave?: () => void; onCopy?: () => void; onDelete?: () => void; @@ -15,6 +17,7 @@ interface ScreenshotPreviewProps { export function ScreenshotPreview({ imageUrl, + dragFilePath, onSave, onCopy, onDelete, @@ -22,37 +25,34 @@ export function ScreenshotPreview({ onClose, className }: ScreenshotPreviewProps) { - const imageRef = useRef(null); const [showActions, setShowActions] = useState(false); const { isLoading } = useScreenshot(); - const handleDragStart = (e: React.DragEvent) => { - e.dataTransfer.effectAllowed = "copy"; - e.dataTransfer.setData("text/uri-list", imageUrl); - e.dataTransfer.setData("text/plain", imageUrl); + const DRAG_THRESHOLD = 5; + const dragStateRef = useRef<{ startX: number; startY: number; pending: boolean } | null>(null); - if (imageRef.current) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - const img = imageRef.current; + const handlePointerDown = useCallback((e: React.PointerEvent) => { + if (e.button !== 0 || !dragFilePath) return; + if ((e.target as HTMLElement).closest('button')) return; + dragStateRef.current = { startX: e.clientX, startY: e.clientY, pending: true }; + }, [dragFilePath]); - const scale = 0.3; - canvas.width = img.naturalWidth * scale; - canvas.height = img.naturalHeight * scale; - - if (ctx) { - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - canvas.toBlob((blob) => { - if (blob) { - const url = URL.createObjectURL(blob); - const dragImage = new Image(); - dragImage.src = url; - e.dataTransfer.setDragImage(dragImage, 0, 0); - } - }); - } + const handlePointerMove = useCallback((e: React.PointerEvent) => { + const state = dragStateRef.current; + if (!state?.pending || !dragFilePath) return; + const dx = e.clientX - state.startX; + const dy = e.clientY - state.startY; + if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) { + dragStateRef.current = null; + startDrag({ item: [dragFilePath], icon: dragFilePath }).catch((err) => { + debugLog('Native drag failed:', err); + }); } - }; + }, [dragFilePath]); + + const handlePointerUp = useCallback(() => { + dragStateRef.current = null; + }, []); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { @@ -70,9 +70,11 @@ export function ScreenshotPreview({ tabIndex={-1} >
setShowActions(true)} onMouseLeave={() => setShowActions(false)} > @@ -85,13 +87,19 @@ export function ScreenshotPreview({ -
- Screenshot preview +
); -} \ No newline at end of file +} diff --git a/src/popup.tsx b/src/popup.tsx index 8dc19fc..03ef9c2 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,7 +1,8 @@ -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { ScreenshotPreview } from '@/components/ScreenshotPreview' import { useScreenshot } from '@/hooks/useScreenshot' +import { invoke } from '@/lib/tauri' import '@/styles.css' function PopupApp() { @@ -14,16 +15,42 @@ function PopupApp() { closePopup } = useScreenshot(); + const [dragFilePath, setDragFilePath] = useState(null); + useEffect(() => { console.log('PopupApp mounted, currentScreenshot:', currentScreenshot); }, [currentScreenshot]); + useEffect(() => { + if (!currentScreenshot) return; + let cancelled = false; + const prepare = async () => { + try { + const path = await invoke('prepare_drag_file', { timestamp: currentScreenshot.timestamp }); + if (cancelled) return; + setDragFilePath(path); + } catch (err) { + console.error('Failed to prepare drag file:', err); + } + }; + prepare(); + return () => { + cancelled = true; + invoke('cleanup_drag_file', { timestamp: currentScreenshot.timestamp }).catch(() => {}); + }; + }, [currentScreenshot]); + useEffect(() => { if (!currentScreenshot) return; const autoDismissTimer = setTimeout(() => { closePopup(); }, 5000); - return () => clearTimeout(autoDismissTimer); + const cancelTimer = () => clearTimeout(autoDismissTimer); + document.addEventListener('pointerdown', cancelTimer, { once: true }); + return () => { + clearTimeout(autoDismissTimer); + document.removeEventListener('pointerdown', cancelTimer); + }; }, [currentScreenshot, closePopup]); useEffect(() => { @@ -60,12 +87,11 @@ function PopupApp() { ); } - const imageUrl = `data:image/png;base64,${currentScreenshot.base64_image}`; - return (
Promise; save_edited_screenshot: (args: { base64Image: string; timestamp: number }) => Promise; copy_edited_screenshot: (args: { base64Image: string; timestamp: number }) => Promise; + prepare_drag_file: (args: { timestamp: number }) => Promise; + cleanup_drag_file: (args: { timestamp: number }) => Promise; get_recent_screenshots: () => Promise>>; copy_screenshot_from_path: (args: { filePath: string }) => Promise; open_in_finder: (args: { filePath: string }) => Promise;