diff --git a/Todo b/Todo index cd32475..72fae64 100644 --- a/Todo +++ b/Todo @@ -1,5 +1,4 @@ Major Revisions: - - Contemplate Anime Support, Add Novel Support (Consumet API) - Moku-Share to Easily Migrate/Share Manga (Investigate Usecase/Feasibility) Minor Revisions: @@ -9,6 +8,8 @@ Minor Revisions: - Add Hover Info on Library (Make sure doesn't conflict with additional clicks) - Revise Migration (https://github.com/Suwayomi/Suwayomi-WebUI/pull/1073) - Look at how Manga are Organized in WebUI and Implement into Series-Detail (Chapter Display is Off) + - Adjustment in Settings for Theme Editor: + - Patch Color-Picker to Work Properly Priority Bugs: - Cache ALL Cover Pictures & Details for Manga in Library @@ -25,8 +26,16 @@ In-Progress:` - Enable Cloudflare Bypass (Suwayomi Config) (Requires Patching) - Fix NSFW Parsing (Appears to not Work???) - - Adjustment in Settings for Theme Editor: - - Patch Color-Picker to Work Properly + - Check & Fix Zoom System + - Incredibly zoomed in on Windows (Appears to work fine on 1440p) + - Zoom Values are Incorrect + - Global Zoom should only scale Reader UI, not Manga + + - Fix Resume-from-Read + - Start on Chapter 46 -> Go all the way to Chapter 47 (Page 28) + - Results in Opening Chapter 46 to take to last page of Chapter (Cache not Cleared). + - Add Event that if different chapter is opened, cache is cleared on all previous chapters. + - Add into Settings diff --git a/dev.moku.app.yml b/dev.moku.app.yml index 8266021..06c4996 100644 --- a/dev.moku.app.yml +++ b/dev.moku.app.yml @@ -181,7 +181,7 @@ modules: path: . - type: file path: packaging/frontend-dist.tar.gz - sha256: e5b4e81c241bfd6940cea0f4815f36ce0f0260fae7249e90d56926b8cafe8016 + sha256: fb01fc1a98499aeb5cf3e464c430a94c78ab1e68f15220ea8f95091f6ca593f2 - packaging/cargo-sources.json - type: inline dest: src-tauri/.cargo diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4b4a6ab..0eb945b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -61,10 +61,14 @@ fn resolve_downloads_path(downloads_path: &str) -> PathBuf { if !downloads_path.trim().is_empty() { return PathBuf::from(downloads_path); } + // Mirror Suwayomi-Server's own default: /Tachidesk/downloads + // Windows: %LOCALAPPDATA%\Tachidesk\downloads + // macOS: ~/Library/Application Support/Tachidesk/downloads + // Linux: $XDG_DATA_HOME/Tachidesk/downloads (~/.local/share/Tachidesk/downloads) let base = std::env::var("XDG_DATA_HOME") .map(PathBuf::from) .unwrap_or_else(|_| dirs::data_dir().unwrap_or_else(|| PathBuf::from("/"))); - base.join("Tachidesk/downloads") + base.join("Tachidesk").join("downloads") } #[tauri::command] @@ -104,6 +108,82 @@ fn get_storage_info(downloads_path: String) -> Result { }) } +/// Returns the resolved default downloads path for the current platform. +/// This mirrors resolve_downloads_path("") so the frontend can display it. +#[tauri::command] +fn get_default_downloads_path() -> String { + resolve_downloads_path("").to_string_lossy().into_owned() +} + +/// Returns true if the given path exists and is a directory. +#[tauri::command] +fn check_path_exists(path: String) -> bool { + std::path::Path::new(path.trim()).is_dir() +} + +/// Creates a directory and all missing parent directories. +#[tauri::command] +fn create_directory(path: String) -> Result<(), String> { + std::fs::create_dir_all(path.trim()).map_err(|e| e.to_string()) +} + +/// Moves all content from `src` into `dst`, then removes `src`. +/// Emits `migrate_progress` events: `{ done, total, current }`. +/// Only deletes the source tree after every file is confirmed copied. +#[tauri::command] +async fn migrate_downloads( + app: tauri::AppHandle, + src: String, + dst: String, +) -> Result<(), String> { + use tauri::Emitter; + use std::fs; + + let src_path = std::path::PathBuf::from(src.trim()); + let dst_path = std::path::PathBuf::from(dst.trim()); + + if !src_path.is_dir() { + return Ok(()); // nothing to migrate + } + + // Count files first so the frontend can show accurate progress + let total: u64 = WalkDir::new(&src_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + .count() as u64; + + let _ = app.emit("migrate_progress", serde_json::json!({ + "done": 0u64, "total": total, "current": "" + })); + + let mut done: u64 = 0; + + for entry in WalkDir::new(&src_path).into_iter().filter_map(|e| e.ok()) { + let rel = entry.path().strip_prefix(&src_path).map_err(|e| e.to_string())?; + let target = dst_path.join(rel); + + if entry.file_type().is_dir() { + fs::create_dir_all(&target).map_err(|e| e.to_string())?; + } else { + if let Some(parent) = target.parent() { + fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + fs::copy(entry.path(), &target).map_err(|e| e.to_string())?; + done += 1; + let _ = app.emit("migrate_progress", serde_json::json!({ + "done": done, + "total": total, + "current": rel.to_string_lossy() + })); + } + } + + // Only remove source after all files are confirmed copied + fs::remove_dir_all(&src_path).map_err(|e| e.to_string())?; + Ok(()) +} + /// Returns the OS/monitor DPI scale factor for the window's current monitor. /// This is the real hardware scale — 1.0 on standard displays, 2.0 on HiDPI/4K, /// 1.25–1.5 on Windows displays with OS-level scaling applied. @@ -651,6 +731,10 @@ pub fn run() { .manage(ServerState(Mutex::new(None))) .invoke_handler(tauri::generate_handler![ get_storage_info, + get_default_downloads_path, + check_path_exists, + create_directory, + migrate_downloads, spawn_server, kill_server, get_platform_ui_scale, diff --git a/src/App.svelte b/src/App.svelte index 195c1f6..62c1a5e 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -78,13 +78,37 @@ let platformScale = $state(1.0); + // Track last applied zoom so we only touch the DOM when the value actually changes. + let _appliedZoom = -1; + let _vhRafId: number | null = null; + function applyZoom() { - const uiZoom = store.settings.uiZoom ?? 1.0; - const effective = platformScale * uiZoom; - const pct = effective * 100; - document.documentElement.style.zoom = `${pct}%`; - document.documentElement.style.setProperty("--ui-scale", String(effective)); - document.documentElement.style.setProperty("--visual-vh", `${window.innerHeight / effective}px`); + const uiZoom = store.settings.uiZoom ?? 1.0; + // Only touch the DOM when the zoom value has genuinely changed. + if (uiZoom === _appliedZoom) return; + _appliedZoom = uiZoom; + + const pct = uiZoom * 100; + document.documentElement.style.setProperty("--ui-zoom", String(uiZoom)); + document.documentElement.style.setProperty("--ui-scale", String(uiZoom)); + + // Only scale the non-reader shell. The reader mounts as a fixed overlay + // and manages its own zoom — applying document-level zoom to it would + // double-scale manga images. + const shell = document.getElementById("app-shell"); + if (shell) { + (shell as HTMLElement).style.zoom = `${pct}%`; + } else { + document.documentElement.style.zoom = `${pct}%`; + } + + // Defer --visual-vh until after the browser has re-laid-out at the new + // zoom level so we read a stable innerHeight, not a mid-transition value. + if (_vhRafId !== null) cancelAnimationFrame(_vhRafId); + _vhRafId = requestAnimationFrame(() => { + _vhRafId = null; + document.documentElement.style.setProperty("--visual-vh", `${window.innerHeight / uiZoom}px`); + }); } let prevQueue: DownloadQueueItem[] = []; @@ -130,7 +154,10 @@ }); $effect(() => { - store.settings.uiZoom; platformScale; + // Re-run only when uiZoom actually changes. platformScale is handled + // directly inside onScaleChanged so it doesn't trigger spurious re-runs + // of this effect on unrelated reactive flushes. + void store.settings.uiZoom; applyZoom(); }); @@ -218,6 +245,9 @@ document.addEventListener("contextmenu", e => e.preventDefault()); (window as any).__mokuShowSplash = () => devSplash = true; + // We read the scale factor so onScaleChanged can re-trigger applyZoom when + // the window moves to a different-DPI monitor, but we do NOT fold it into + // the zoom math — Tauri's WebView already accounts for DPI scaling. platformScale = await invoke("get_platform_ui_scale").catch(() => 1.0); applyZoom(); @@ -325,7 +355,7 @@ onRetry={handleRetry} onBypass={handleBypass} /> {:else} -
+
{#if idle && !store.activeChapter} { idle = false; resetIdle(); }} /> diff --git a/src/components/reader/Reader.svelte b/src/components/reader/Reader.svelte index c1ae352..cc5db64 100644 --- a/src/components/reader/Reader.svelte +++ b/src/components/reader/Reader.svelte @@ -1,25 +1,20 @@