diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7415e42..5df899b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -670,6 +670,7 @@ name = "chrono-snap" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "chrono", "image", "serde", @@ -2023,6 +2024,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -4902,6 +4909,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", + "http-range", "jni", "libc", "log", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c7cbbc8..8aa8f2a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = ["tray-icon"] } +tauri = { version = "2", features = ["protocol-asset", "tray-icon"] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -30,6 +30,7 @@ image = "0.25.10" chrono = "0.4.44" tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "time"] } anyhow = "1.0.102" +base64 = "0.22.1" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-autostart = "2" diff --git a/src-tauri/src/engine.rs b/src-tauri/src/engine.rs index 047bec7..0887adb 100644 --- a/src-tauri/src/engine.rs +++ b/src-tauri/src/engine.rs @@ -24,6 +24,14 @@ pub fn get_pause_state(state: tauri::State<'_, AppState>) -> bool { state.is_paused.load(Ordering::SeqCst) } +use base64::{engine::general_purpose, Engine as _}; + +#[tauri::command] +pub fn get_image_base64(path: String) -> Result { + let bytes = fs::read(path).map_err(|e| e.to_string())?; + Ok(general_purpose::STANDARD.encode(bytes)) +} + #[tauri::command] pub fn get_timeline(date: String, base_dir: String) -> Vec { let mut results = Vec::new(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 49bac72..28d26cc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19,7 +19,8 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ engine::toggle_pause, engine::get_pause_state, - engine::get_timeline + engine::get_timeline, + engine::get_image_base64 ]) .setup(|app| { app.manage(engine::AppState { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 48176dd..ce8da1a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -18,7 +18,11 @@ } ], "security": { - "csp": null + "csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost tauri: http://localhost:*", + "assetProtocol": { + "enable": true, + "scope": ["**"] + } } }, "bundle": { diff --git a/src/App.vue b/src/App.vue index 2e6e3a2..564c092 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,6 +12,7 @@ const isPaused = ref(false); const currentDate = ref(new Date().toISOString().split("T")[0]); // YYYY-MM-DD const timelineImages = ref<{ time: string; path: string }[]>([]); const selectedImage = ref<{ time: string; path: string } | null>(null); +const previewSrc = ref(""); const isFullscreen = ref(false); const isSettingsOpen = ref(false); const mergeScreens = ref(false); @@ -59,11 +60,6 @@ const togglePauseState = async () => { const loadTimeline = async () => { if (!savePath.value) return; const dateStr = currentDate.value; - // Rust is joining paths, Tauri FS plugin has a resolve function or we can use string concat. - // Wait, readDir with an absolute path requires specific permissions in Tauri v2, or we need to use Rust to list files. - // Actually, standard `readDir` supports absolute paths if we configure scope in capabilities. - // We didn't allow `$fs:allow-read-all` or similar, we only added `fs:default`. - // The simplest way to fetch timeline is a custom Rust command since we already manage the paths there! // It's safer to read files from Rust and return a list of { time, absolute_path }. timelineImages.value = await invoke("get_timeline", { date: dateStr, baseDir: savePath.value }); }; @@ -72,8 +68,15 @@ const refresh = async () => { await loadTimeline(); }; -const selectImage = (img: { time: string; path: string }) => { +const selectImage = async (img: { time: string; path: string }) => { selectedImage.value = img; + try { + const base64 = await invoke("get_image_base64", { path: img.path }); + previewSrc.value = `data:image/jpeg;base64,${base64}`; + } catch (e) { + console.error("Failed to load image:", e); + previewSrc.value = ""; + } }; const getSrc = (path: string) => { @@ -160,7 +163,7 @@ const getSrc = (path: string) => {