fix image preview

This commit is contained in:
Julian Freeman
2026-03-22 15:46:13 -04:00
parent e3cba2a6ba
commit 7a82f3fb81
6 changed files with 36 additions and 11 deletions

8
src-tauri/Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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<String, String> {
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<serde_json::Value> {
let mut results = Vec::new();

View File

@@ -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 {

View File

@@ -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": {

View File

@@ -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) => {
<div v-if="selectedImage" class="flex-1 p-10 flex items-center justify-center overflow-hidden">
<div class="relative max-w-full max-h-full group">
<img
:src="getSrc(selectedImage.path)"
:src="previewSrc"
class="max-w-full max-h-full object-contain rounded-2xl shadow-[0_12px_30px_rgba(0,0,0,0.08)]"
/>
<button
@@ -259,7 +262,7 @@ const getSrc = (path: string) => {
<X :size="32" />
</button>
<img
:src="getSrc(selectedImage.path)"
:src="previewSrc"
class="max-w-full max-h-full object-contain"
/>
</div>