diff --git a/public/images/lmrp.png b/public/images/lmrp.png deleted file mode 100644 index 09a57ed..0000000 Binary files a/public/images/lmrp.png and /dev/null differ diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fd717f3..f051625 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -284,6 +284,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -512,6 +518,12 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -624,12 +636,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -911,6 +948,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.8" @@ -937,7 +980,9 @@ version = "1.0.1" dependencies = [ "base64 0.21.7", "futures-util", + "image 0.24.9", "libc", + "percent-encoding", "reqwest 0.11.27", "rfd", "serde", @@ -1055,6 +1100,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -1424,6 +1484,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gilrs" version = "0.11.1" @@ -1627,6 +1697,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1990,6 +2071,24 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png 0.17.16", + "qoi", + "tiff", +] + [[package]] name = "image" version = "0.25.10" @@ -2163,6 +2262,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.94" @@ -2226,6 +2334,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -3212,6 +3326,15 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-xml" version = "0.38.4" @@ -3367,6 +3490,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -4279,7 +4422,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http 1.4.0", - "image", + "image 0.25.10", "jni", "libc", "log", @@ -4622,6 +4765,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.47" @@ -5440,6 +5594,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -6361,6 +6521,15 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zvariant" version = "5.10.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 02cd558..e561026 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri = { version = "2", features = [ "tray-icon", "image-png"] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -26,3 +26,5 @@ tokio-util = { version = "0.7.18", features = ["rt"] } base64 = "0.21" rfd = "0.15" libc = "0.2" +image = "0.24" +percent-encoding = "2.3" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index e8589d1..e30d292 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -1,26 +1,30 @@ -{ - "identifier": "default", - "description": "Default permissions", - "windows": [ - "main" - ], - "permissions": [ - "core:default", - "core:window:allow-minimize", - "core:window:allow-maximize", - "core:window:allow-toggle-maximize", - "core:window:allow-close", - "core:window:allow-set-title", - "core:window:allow-start-dragging", - "core:window:allow-show", - "core:window:allow-hide", - "core:window:allow-unminimize", - "core:window:allow-set-focus", - "opener:default", - "gamepad:default", - "drpc:allow-is-running", - "drpc:default", - "core:event:allow-listen", - "core:app:default" - ] +{ + "identifier": "default", + "description": "Default permissions", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "core:app:default", + "core:event:default", + "core:image:default", + "core:menu:default", + "core:path:default", + "core:resources:default", + "core:tray:default", + "core:webview:default", + "core:window:default", + "opener:default", + "gamepad:default", + "drpc:default", + { + "identifier": "opener:allow-open-path", + "allow": [{ "path": "**" }] + }, + { + "identifier": "opener:allow-reveal-item-in-dir", + "allow": [{ "path": "**" }] + } + ] } \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index be9e6b7..8397e8d 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"default":{"identifier":"default","description":"Default permissions","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-close","core:window:allow-set-title","core:window:allow-start-dragging","core:window:allow-show","core:window:allow-hide","core:window:allow-unminimize","core:window:allow-set-focus","opener:default","gamepad:default","drpc:allow-is-running","drpc:default","core:event:allow-listen","core:app:default"]}} \ No newline at end of file +{"default":{"identifier":"default","description":"Default permissions","local":true,"windows":["main"],"permissions":["core:default","core:app:default","core:event:default","core:image:default","core:menu:default","core:path:default","core:resources:default","core:tray:default","core:webview:default","core:window:default","opener:default","gamepad:default","drpc:default",{"identifier":"opener:allow-open-path","allow":[{"path":"**"}]},{"identifier":"opener:allow-reveal-item-in-dir","allow":[{"path":"**"}]}]}} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 97f9d2e..0ffcf86 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -73,6 +73,15 @@ pub struct Runner { pub r#type: String, } +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ScreenshotInfo { + pub path: String, + pub instance_id: String, + pub name: String, + pub date: u64, +} + pub struct DownloadState { pub token: Arc>> } pub struct GameState { pub child: Arc>> } @@ -1295,6 +1304,46 @@ async fn fetch_skin(username: String) -> Result<(String, String), String> { Ok((image_b64.to_string(), name_exact)) } +#[tauri::command] +fn get_screenshots(app: AppHandle) -> Vec { + let mut screenshots = Vec::new(); + let instances_dir = get_app_dir(&app).join("instances"); + let _ = fs::create_dir_all(&instances_dir); + if let Ok(entries) = fs::read_dir(instances_dir) { + for entry in entries.flatten() { + if entry.path().is_dir() { + let instance_id = entry.file_name().to_string_lossy().to_string(); + let screenshots_dir = entry.path().join("screenshots"); + if let Ok(files) = fs::read_dir(screenshots_dir) { + for file in files.flatten() { + let path = file.path(); + if path.extension().and_then(|s| s.to_str()) == Some("png") { + let name = path.file_name().unwrap_or_default().to_string_lossy().to_string(); + let date = path.metadata().and_then(|m| m.modified()).ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs()) + .unwrap_or(0); + screenshots.push(ScreenshotInfo { + path: path.to_string_lossy().to_string(), + instance_id: instance_id.clone(), + name, + date, + }); + } + } + } + } + } + } + screenshots.sort_by(|a, b| b.date.cmp(&a.date)); + screenshots +} + +#[tauri::command] +fn delete_screenshot(path: String) -> Result<(), String> { + fs::remove_file(path).map_err(|e| e.to_string()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -1303,7 +1352,28 @@ pub fn run() { .plugin(tauri_plugin_gamepad::init()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_drpc::init()) - .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]) + .register_uri_scheme_protocol("screenshots", |_app, request| { + let uri = request.uri().path(); + let decoded_path = percent_encoding::percent_decode_str(uri).decode_utf8_lossy(); + let path = std::path::Path::new(&*decoded_path); + + match std::fs::read(path) { + Ok(data) => { + tauri::http::Response::builder() + .header("Content-Type", "image/png") + .header("Access-Control-Allow-Origin", "*") + .body(data) + .unwrap() + } + Err(_) => { + tauri::http::Response::builder() + .status(404) + .body(Vec::new()) + .unwrap() + } + } + }) + .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, get_screenshots, delete_screenshot]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4772313..924085e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -26,7 +26,7 @@ } ], "security": { - "csp": null + "csp": "default-src 'self'; img-src 'self' screenshots: asset: https://asset.localhost http://asset.localhost; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' https://* http://* screenshots: asset:; script-src 'self' 'unsafe-inline' 'unsafe-eval';" } }, "bundle": { diff --git a/src/components/common/SkinViewer.tsx b/src/components/common/SkinViewer.tsx index 71c8235..fe924f5 100644 --- a/src/components/common/SkinViewer.tsx +++ b/src/components/common/SkinViewer.tsx @@ -211,16 +211,16 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo if (e.key === 'ArrowRight') { if (legacyMode) onNavigateRight(); - else if (focusIndex === 3) onNavigateRight(); - else if (focusIndex === 1 || focusIndex === 2) setFocusIndex(prev => prev + 1); + else if (focusIndex === 4) onNavigateRight(); + else if (focusIndex >= 1 && focusIndex < 4) setFocusIndex(prev => prev + 1); } else if (e.key === 'ArrowLeft') { if (legacyMode) return; - if (focusIndex === 2 || focusIndex === 3) setFocusIndex(prev => prev - 1); + if (focusIndex > 1 && focusIndex <= 4) setFocusIndex(prev => prev - 1); } else if (e.key === 'ArrowDown') { if (legacyMode) { - return; + setFocusIndex(prev => (prev < 4 ? prev + 1 : prev)); } else { - setFocusIndex(prev => (prev < 3 ? prev + 1 : prev)); + setFocusIndex(prev => (prev < 4 ? prev + 1 : prev)); } } else if (e.key === 'ArrowUp') { if (legacyMode) { @@ -240,6 +240,9 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo } else if (focusIndex === 3) { playPressSound(); setSkinUrl('/images/Default.png'); + } else if (focusIndex === 4) { + playPressSound(); + setActiveView('screenshots'); } } }; @@ -320,6 +323,16 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo Delete )} + ); diff --git a/src/components/views/HomeView.tsx b/src/components/views/HomeView.tsx index 96f263d..1d3bd25 100644 --- a/src/components/views/HomeView.tsx +++ b/src/components/views/HomeView.tsx @@ -47,7 +47,7 @@ const HomeView = memo(function HomeView() { : isGameRunning ? stopGame : isDownloading - ? () => {} + ? () => { } : isInstalled ? handleLaunch : () => toggleInstall(profile), @@ -105,35 +105,37 @@ const HomeView = memo(function HomeView() { animate={{ opacity: isFocusedSection ? 1 : 0.5, y: 0 }} exit={{ opacity: 0, y: 10 }} transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }} - className="w-full max-w-[540px] flex flex-col space-y-3 outline-none" + className="relative w-full max-w-[540px] flex flex-col space-y-3 outline-none" > {buttons.map((btn: any, i: number) => ( - +
+ +
))} + {!legacyMode && (
diff --git a/src/components/views/ScreenshotsView.tsx b/src/components/views/ScreenshotsView.tsx new file mode 100644 index 0000000..b4eaf94 --- /dev/null +++ b/src/components/views/ScreenshotsView.tsx @@ -0,0 +1,218 @@ +import { useState, useEffect, memo } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { useUI, useAudio, useGame, useConfig } from "../../context/LauncherContext"; +import { ScreenshotService, ScreenshotInfo } from "../../services/ScreenshotService"; +const ScreenshotsView = memo(function ScreenshotsView() { + const { setActiveView } = useUI(); + const { playPressSound, playBackSound } = useAudio(); + const { editions } = useGame(); + const { animationsEnabled } = useConfig(); + const [screenshots, setScreenshots] = useState([]); + const [selectedScreenshot, setSelectedScreenshot] = useState(null); + const [loading, setLoading] = useState(true); + const [gridFocusIndex, setGridFocusIndex] = useState(0); + const [modalFocusIndex, setModalFocusIndex] = useState(0); + + useEffect(() => { + ScreenshotService.getScreenshots().then((data) => { + setScreenshots(data); + setLoading(false); + }); + }, []); + + const handleBack = () => { + playBackSound(); + 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); + } + }; + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (loading) return; + + if (selectedScreenshot) { + if (e.key === "Escape") { + 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)); + } else if (e.key === "Enter") { + if (modalFocusIndex === 0) handleDelete(selectedScreenshot); + else if (modalFocusIndex === 1) { + playBackSound(); + setSelectedScreenshot(null); + } + } + return; + } + + const cols = window.innerWidth >= 1024 ? 4 : window.innerWidth >= 768 ? 3 : 2; + + if (e.key === "Escape") { + handleBack(); + } else if (e.key === "ArrowLeft") { + setGridFocusIndex((prev) => (prev > 0 ? prev - 1 : prev)); + } else if (e.key === "ArrowRight") { + setGridFocusIndex((prev) => (prev < screenshots.length - 1 ? prev + 1 : prev)); + } else if (e.key === "ArrowUp") { + setGridFocusIndex((prev) => (prev >= cols ? prev - cols : prev)); + } else if (e.key === "ArrowDown") { + setGridFocusIndex((prev) => (prev <= screenshots.length - 1 - cols ? prev + cols : prev)); + } else if (e.key === "Enter") { + if (screenshots[gridFocusIndex]) { + playPressSound(); + setModalFocusIndex(0); + setSelectedScreenshot(screenshots[gridFocusIndex]); + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [loading, selectedScreenshot, gridFocusIndex, modalFocusIndex, screenshots]); + + useEffect(() => { + if (!selectedScreenshot) { + const element = document.getElementById(`ss-${gridFocusIndex}`); + element?.scrollIntoView({ block: "nearest", behavior: "smooth" }); + } + }, [gridFocusIndex, selectedScreenshot]); + + const getEditionLogo = (instanceId: string) => { + const edition = editions.find((e: any) => e.id === instanceId); + return edition?.logo || edition?.titleImage; + }; + + return ( + +
+

+ Screenshots +

+ +
+ +
+ {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"; + }} + /> + +
+ + +
+ +
+ {selectedScreenshot.name} - {new Date(selectedScreenshot.date * 1000).toLocaleString()} +
+
+
+ )} +
+ + ); +}); + +export default ScreenshotsView; diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 250d334..432e66c 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -9,6 +9,7 @@ import WorkshopView from "../components/views/WorkshopView"; import SetupView from "../components/views/SetupView"; import PckEditorView from "../components/views/PckEditorView"; import { ArcEditorView } from "../components/views/ArcEditorView"; +import ScreenshotsView from "../components/views/ScreenshotsView"; import SkinViewer from "../components/common/SkinViewer"; import TeamModal from "../components/modals/TeamModal"; import PanoramaBackground from "../components/common/PanoramaBackground"; @@ -348,6 +349,9 @@ export default function App() { )} {activeView === "skins" && } + {activeView === "screenshots" && ( + + )}
diff --git a/src/services/ScreenshotService.ts b/src/services/ScreenshotService.ts new file mode 100644 index 0000000..8ef405d --- /dev/null +++ b/src/services/ScreenshotService.ts @@ -0,0 +1,17 @@ +import { invoke } from "@tauri-apps/api/core"; +export interface ScreenshotInfo { + path: string; + instanceId: string; + name: string; + date: number; +} + +export class ScreenshotService { + static async getScreenshots(): Promise { + return invoke("get_screenshots"); + } + + static async deleteScreenshot(path: string): Promise { + return invoke("delete_screenshot", { path }); + } +}