fix image preview
This commit is contained in:
8
src-tauri/Cargo.lock
generated
8
src-tauri/Cargo.lock
generated
@@ -670,6 +670,7 @@ name = "chrono-snap"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"image",
|
"image",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2023,6 +2024,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
@@ -4902,6 +4909,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"http-range",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = ["tray-icon"] }
|
tauri = { version = "2", features = ["protocol-asset", "tray-icon"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -30,6 +30,7 @@ image = "0.25.10"
|
|||||||
chrono = "0.4.44"
|
chrono = "0.4.44"
|
||||||
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
anyhow = "1.0.102"
|
anyhow = "1.0.102"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-autostart = "2"
|
tauri-plugin-autostart = "2"
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ pub fn get_pause_state(state: tauri::State<'_, AppState>) -> bool {
|
|||||||
state.is_paused.load(Ordering::SeqCst)
|
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]
|
#[tauri::command]
|
||||||
pub fn get_timeline(date: String, base_dir: String) -> Vec<serde_json::Value> {
|
pub fn get_timeline(date: String, base_dir: String) -> Vec<serde_json::Value> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
engine::toggle_pause,
|
engine::toggle_pause,
|
||||||
engine::get_pause_state,
|
engine::get_pause_state,
|
||||||
engine::get_timeline
|
engine::get_timeline,
|
||||||
|
engine::get_image_base64
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
app.manage(engine::AppState {
|
app.manage(engine::AppState {
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost tauri: http://localhost:*",
|
||||||
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": ["**"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
19
src/App.vue
19
src/App.vue
@@ -12,6 +12,7 @@ const isPaused = ref(false);
|
|||||||
const currentDate = ref(new Date().toISOString().split("T")[0]); // YYYY-MM-DD
|
const currentDate = ref(new Date().toISOString().split("T")[0]); // YYYY-MM-DD
|
||||||
const timelineImages = ref<{ time: string; path: string }[]>([]);
|
const timelineImages = ref<{ time: string; path: string }[]>([]);
|
||||||
const selectedImage = ref<{ time: string; path: string } | null>(null);
|
const selectedImage = ref<{ time: string; path: string } | null>(null);
|
||||||
|
const previewSrc = ref("");
|
||||||
const isFullscreen = ref(false);
|
const isFullscreen = ref(false);
|
||||||
const isSettingsOpen = ref(false);
|
const isSettingsOpen = ref(false);
|
||||||
const mergeScreens = ref(false);
|
const mergeScreens = ref(false);
|
||||||
@@ -59,11 +60,6 @@ const togglePauseState = async () => {
|
|||||||
const loadTimeline = async () => {
|
const loadTimeline = async () => {
|
||||||
if (!savePath.value) return;
|
if (!savePath.value) return;
|
||||||
const dateStr = currentDate.value;
|
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 }.
|
// 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 });
|
timelineImages.value = await invoke("get_timeline", { date: dateStr, baseDir: savePath.value });
|
||||||
};
|
};
|
||||||
@@ -72,8 +68,15 @@ const refresh = async () => {
|
|||||||
await loadTimeline();
|
await loadTimeline();
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectImage = (img: { time: string; path: string }) => {
|
const selectImage = async (img: { time: string; path: string }) => {
|
||||||
selectedImage.value = img;
|
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) => {
|
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 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">
|
<div class="relative max-w-full max-h-full group">
|
||||||
<img
|
<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)]"
|
class="max-w-full max-h-full object-contain rounded-2xl shadow-[0_12px_30px_rgba(0,0,0,0.08)]"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -259,7 +262,7 @@ const getSrc = (path: string) => {
|
|||||||
<X :size="32" />
|
<X :size="32" />
|
||||||
</button>
|
</button>
|
||||||
<img
|
<img
|
||||||
:src="getSrc(selectedImage.path)"
|
:src="previewSrc"
|
||||||
class="max-w-full max-h-full object-contain"
|
class="max-w-full max-h-full object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user