diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4b72b01..c1278c5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1453,6 +1453,16 @@ fn delete_screenshot(path: String) -> Result<(), String> { fs::remove_file(path).map_err(|e| e.to_string()) } +#[tauri::command] +fn open_screenshot_folder(app: AppHandle, path: String) { + let p = std::path::Path::new(&path); + if let Some(parent) = p.parent() { + if parent.exists() { + let _ = app.opener().open_path(parent.to_str().unwrap(), None::<&str>); + } + } +} + #[tauri::command] async fn save_global_skin_pck(app: AppHandle, pck_data: Vec) -> Result<(), String> { let app_dir = get_app_dir(&app); @@ -1489,7 +1499,7 @@ pub fn run() { } } }) - .invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, save_global_skin_pck, check_game_update]) + .invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, open_screenshot_folder, save_global_skin_pck, check_game_update]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src/components/views/ScreenshotsView.tsx b/src/components/views/ScreenshotsView.tsx index 9281915..d215569 100644 --- a/src/components/views/ScreenshotsView.tsx +++ b/src/components/views/ScreenshotsView.tsx @@ -12,7 +12,8 @@ const ScreenshotsView = memo(function ScreenshotsView() { const [loading, setLoading] = useState(true); const [gridFocusIndex, setGridFocusIndex] = useState(0); const [modalFocusIndex, setModalFocusIndex] = useState(0); - + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [deleteConfirmFocusIndex, setDeleteConfirmFocusIndex] = useState(0); useEffect(() => { ScreenshotService.getScreenshots().then((data) => { setScreenshots(data); @@ -25,30 +26,60 @@ const ScreenshotsView = memo(function ScreenshotsView() { setActiveView("main"); }; - const handleDelete = async (screenshot: ScreenshotInfo) => { - if (confirm("Are you sure you want to delete this screenshot?")) { - playPressSound(); - await ScreenshotService.deleteScreenshot(screenshot.path); - setScreenshots((prev) => prev.filter((s) => s.path !== screenshot.path)); - setSelectedScreenshot(null); - } + const handleOpenFolder = (screenshot: ScreenshotInfo) => { + playPressSound(); + ScreenshotService.showInFolder(screenshot.path); + }; + + const confirmDelete = async () => { + if (!selectedScreenshot) return; + playPressSound(); + await ScreenshotService.deleteScreenshot(selectedScreenshot.path); + setScreenshots((prev) => prev.filter((s) => s.path !== selectedScreenshot.path)); + setSelectedScreenshot(null); + setShowDeleteConfirm(false); }; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (loading) return; + if (showDeleteConfirm) { + if (e.key === "Escape" || e.key === "Backspace") { + playBackSound(); + setShowDeleteConfirm(false); + } else if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "Tab") { + e.preventDefault(); + playPressSound(); + setDeleteConfirmFocusIndex((prev) => (prev === 0 ? 1 : 0)); + } else if (e.key === "Enter") { + if (deleteConfirmFocusIndex === 1) confirmDelete(); + else { + playBackSound(); + setShowDeleteConfirm(false); + } + } + return; + } if (selectedScreenshot) { - if (e.key === "Escape") { + if (e.key === "Escape" || e.key === "Backspace") { playBackSound(); setSelectedScreenshot(null); } else if (e.key === "ArrowLeft") { - setModalFocusIndex((prev) => (prev > 0 ? prev - 1 : prev)); - } else if (e.key === "ArrowRight") { - setModalFocusIndex((prev) => (prev < 2 ? prev + 1 : prev)); + playPressSound(); + setModalFocusIndex((prev) => (prev > 0 ? prev - 1 : 2)); + } else if (e.key === "ArrowRight" || e.key === "Tab") { + e.preventDefault(); + playPressSound(); + setModalFocusIndex((prev) => (prev < 2 ? prev + 1 : 0)); } else if (e.key === "Enter") { - if (modalFocusIndex === 0) handleDelete(selectedScreenshot); + if (modalFocusIndex === 0) handleOpenFolder(selectedScreenshot); else if (modalFocusIndex === 1) { + playPressSound(); + setDeleteConfirmFocusIndex(0); + setShowDeleteConfirm(true); + } + else if (modalFocusIndex === 2) { playBackSound(); setSelectedScreenshot(null); } @@ -57,8 +88,7 @@ const ScreenshotsView = memo(function ScreenshotsView() { } const cols = window.innerWidth >= 1024 ? 4 : window.innerWidth >= 768 ? 3 : 2; - - if (e.key === "Escape") { + if (e.key === "Escape" || e.key === "Backspace") { handleBack(); } else if (e.key === "ArrowLeft") { setGridFocusIndex((prev) => (prev > 0 ? prev - 1 : prev)); @@ -71,7 +101,7 @@ const ScreenshotsView = memo(function ScreenshotsView() { } else if (e.key === "Enter") { if (screenshots[gridFocusIndex]) { playPressSound(); - setModalFocusIndex(0); + setModalFocusIndex(2); setSelectedScreenshot(screenshots[gridFocusIndex]); } } @@ -79,7 +109,7 @@ const ScreenshotsView = memo(function ScreenshotsView() { window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [loading, selectedScreenshot, gridFocusIndex, modalFocusIndex, screenshots]); + }, [loading, selectedScreenshot, gridFocusIndex, modalFocusIndex, screenshots, showDeleteConfirm, deleteConfirmFocusIndex]); useEffect(() => { if (!selectedScreenshot) { @@ -99,113 +129,259 @@ const ScreenshotsView = memo(function ScreenshotsView() { animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: animationsEnabled ? 0.3 : 0 }} - className="flex flex-col items-center w-full h-full max-w-5xl" + className="flex flex-col items-center w-full h-full max-w-6xl relative font-['Mojangles'] text-white select-none outline-none focus:outline-none" > -
-

- Screenshots -

+

+ Screenshots +

+ +
+
+ {loading ? ( +
+ Scanning Archives... +
+ ) : screenshots.length === 0 ? ( +
+ No screenshots found + Take some in-game with F2 +
+ ) : ( +
+ {screenshots.map((ss, index) => ( +
{ + setGridFocusIndex(index); + setModalFocusIndex(2); // Close button + setSelectedScreenshot(ss); + playPressSound(); + }} + onMouseEnter={() => setGridFocusIndex(index)} + className={` + relative aspect-video flex flex-col cursor-pointer transition-all border-2 rounded-sm overflow-hidden bg-black/40 + ${gridFocusIndex === index ? "border-[#FFFF55] scale-105 z-10" : "border-[#333] hover:border-[#FFFF55]"} + `} + style={{ + backgroundImage: "url('/images/frame_background.png')", + backgroundSize: '100% 100%', + imageRendering: 'pixelated', + boxShadow: gridFocusIndex === index ? '0 0 20px rgba(255, 255, 85, 0.2)' : 'none', + }} + > +
+ {ss.name} { + (e.target as HTMLImageElement).src = "/images/Folder_Icon.png"; + }} + /> +
+ + {getEditionLogo(ss.instanceId) && ( +
+ +
+ )} + +
+ {new Date(ss.date * 1000).toLocaleDateString()} +
+
+
+ ))} +
+ )} +
+
+ +
-
- {loading ? ( -
- Scanning for screenshots... -
- ) : screenshots.length === 0 ? ( -
- No screenshots found. - Take some in-game with F2! -
- ) : ( -
- {screenshots.map((ss, index) => ( - { - setGridFocusIndex(index); - setSelectedScreenshot(ss); - }} - className={`relative aspect-video bg-black/40 border-2 transition-all cursor-pointer overflow-hidden group shadow-lg ${gridFocusIndex === index ? "border-[#FFFF55] scale-105" : "border-transparent"}`} - > - {ss.name} { - (e.target as HTMLImageElement).src = "/images/Folder_Icon.png"; - }} - /> -
- {getEditionLogo(ss.instanceId) && ( - - )} -
- {new Date(ss.date * 1000).toLocaleDateString()} -
- - ))} -
- )} -
- {selectedScreenshot && ( setSelectedScreenshot(null)} > e.stopPropagation()} > - { - (e.target as HTMLImageElement).src = "/images/Pack_Icon.png"; - }} - /> - -
- - +
+ { + (e.target as HTMLImageElement).src = "/images/Pack_Icon.png"; + }} + /> +
+
+ + {selectedScreenshot.name} + + + Captured on {new Date(selectedScreenshot.date * 1000).toLocaleString()} + +
+ {getEditionLogo(selectedScreenshot.instanceId) && ( + + )} +
-
- {selectedScreenshot.name} - {new Date(selectedScreenshot.date * 1000).toLocaleString()} +
+ + + +
+ + + )} + + + {showDeleteConfirm && ( + setShowDeleteConfirm(false)} + > + e.stopPropagation()} + > + + Are you sure you want to delete this screenshot? + + +
+ +
diff --git a/src/services/ScreenshotService.ts b/src/services/ScreenshotService.ts index 8ef405d..c0f089d 100644 --- a/src/services/ScreenshotService.ts +++ b/src/services/ScreenshotService.ts @@ -14,4 +14,8 @@ export class ScreenshotService { static async deleteScreenshot(path: string): Promise { return invoke("delete_screenshot", { path }); } + + static async showInFolder(path: string): Promise { + return invoke("open_screenshot_folder", { path }); + } }