diff --git a/crates/backend/src/backend_handler.rs b/crates/backend/src/backend_handler.rs index 6db1e5a3..785c3b6e 100644 --- a/crates/backend/src/backend_handler.rs +++ b/crates/backend/src/backend_handler.rs @@ -1687,6 +1687,9 @@ impl BackendState { MessageToBackend::RequestSkinLibrary => { SkinManager::load_skin_library(&self); }, + MessageToBackend::RemoveFromSkinLibrary { skin } => { + SkinManager::remove_skin(&self, skin); + }, MessageToBackend::AddToSkinLibrary { source } => { let (bytes, filename) = match source { bridge::message::UrlOrFile::Url { url } => { diff --git a/crates/backend/src/skin_manager.rs b/crates/backend/src/skin_manager.rs index 22d3f7b4..7fdfc0a2 100644 --- a/crates/backend/src/skin_manager.rs +++ b/crates/backend/src/skin_manager.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, io::Cursor, path::Path, sync::Arc, time::SystemTime}; +use std::{collections::HashMap, io::Cursor, path::{Path, PathBuf}, sync::Arc, time::SystemTime}; use bridge::message::{AccountSkinResult, BridgeDataLoadState, MessageToFrontend, SkinLibrary}; use image::DynamicImage; @@ -17,6 +17,7 @@ pub struct SkinManager { skin_library_last: Vec<(SystemTime, Arc, Arc<[u8]>)>, skin_library: Arc<[Arc<[u8]>]>, skin_library_changes: FolderChanges, + skin_path_map: HashMap, Arc> } impl Default for SkinManager { @@ -28,6 +29,7 @@ impl Default for SkinManager { skin_library_last: Default::default(), skin_library: Default::default(), skin_library_changes: FolderChanges::all_dirty(), + skin_path_map: HashMap::new(), } } } @@ -55,6 +57,18 @@ impl SkinManager { } } + pub fn remove_skin(backend: &BackendState, image_bytes: Arc<[u8]>) { + let skin_manager = backend.skin_manager.write(); + let Some(skin_path) = skin_manager.skin_path_map.get(&image_bytes) else { + log::warn!("Unable to find skin for deletion"); + return; + }; + let Ok(_) = std::fs::remove_file(skin_path) else { + log::warn!("Unable to delete skin at {}", skin_path.display()); + return; + }; + } + pub fn frontend_request( backend: &BackendState, skin_url: Arc, @@ -257,6 +271,7 @@ impl SkinManager { let mut skin_manager = backend.skin_manager.write(); skin_manager.skin_library_last = Default::default(); skin_manager.skin_library = Default::default(); + skin_manager.skin_path_map = Default::default(); backend.send.send(MessageToFrontend::SkinLibraryUpdated { skin_library: SkinLibrary { state: skin_manager.skin_library_state.clone(), @@ -301,6 +316,7 @@ impl SkinManager { let path: Arc = path.into(); + skins.push((time, path, image, bytes)); } @@ -311,6 +327,7 @@ impl SkinManager { (time, path, skin_manager.create_skin(&image, &bytes)) }).collect::>(); skin_manager.skin_library = skins.iter().map(|(_, _, bytes)| bytes.clone()).collect(); + skin_manager.skin_path_map = skins.iter().map(|(_, path, bytes)| (bytes.clone(), path.clone())).collect(); skin_manager.skin_library_last = skins; backend.send.send(MessageToFrontend::SkinLibraryUpdated { skin_library: SkinLibrary { @@ -366,6 +383,7 @@ impl SkinManager { skins.sort_by_key(|(time, _, _)| *time); skin_manager.skin_library = skins.iter().map(|(_, _, bytes)| bytes.clone()).collect(); + skin_manager.skin_path_map = skins.iter().map(|(_, path, bytes)| (bytes.clone(), path.clone())).collect(); skin_manager.skin_library_last = skins; backend.send.send(MessageToFrontend::SkinLibraryUpdated { skin_library: SkinLibrary { diff --git a/crates/bridge/src/message.rs b/crates/bridge/src/message.rs index 667300b4..fd604277 100644 --- a/crates/bridge/src/message.rs +++ b/crates/bridge/src/message.rs @@ -234,6 +234,9 @@ pub enum MessageToBackend { cape: Option, }, RequestSkinLibrary, + RemoveFromSkinLibrary{ + skin: Arc<[u8]>, + }, AddToSkinLibrary { source: UrlOrFile, }, diff --git a/crates/frontend/src/pages/skins_page.rs b/crates/frontend/src/pages/skins_page.rs index bdaa2107..16bd8918 100644 --- a/crates/frontend/src/pages/skins_page.rs +++ b/crates/frontend/src/pages/skins_page.rs @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap; use schema::minecraft_profile::{SkinState, SkinVariant}; use uuid::Uuid; use crate::{ - component::{player_model_widget::PlayerModelWidget, shrinking_text::ShrinkingText}, data_asset_loader::DataAssetLoader, entity::{DataEntities, account::AccountExt}, icon::PandoraIcon, interface_config::InterfaceConfig, pages::page::Page, png_render_cache::ImageTransformation, ts + component::{player_model_widget::PlayerModelWidget, shrinking_text::ShrinkingText}, data_asset_loader::DataAssetLoader, entity::{DataEntities, account::AccountExt}, icon::PandoraIcon, interface_config::InterfaceConfig, pages::page::Page, png_render_cache::ImageTransformation, skin_renderer::{self, determine_skin_variant}, ts }; pub struct SkinsPage { @@ -21,6 +21,7 @@ pub struct SkinsPage { applying_to_account: Option, request_account_skin: Option>, selected_skin: Arc<[u8]>, + active_skin: Arc<[u8]>, selected_cape: Option<(Uuid, Arc)>, active_cape: Option<(Uuid, Arc)>, pending_apply_cape: bool, @@ -42,6 +43,7 @@ impl SkinsPage { applying_to_account: None, request_account_skin: None, selected_skin: DEFAULT_SKIN.clone(), + active_skin: DEFAULT_SKIN.clone(), selected_cape: None, active_cape: None, pending_apply_cape: false, @@ -105,6 +107,7 @@ impl SkinsPage { if let AccountSkinResult::Success { skin, variant } = &skin_result { if let Some(skin) = skin.clone() { page.selected_skin = skin.clone(); + page.active_skin = skin.clone(); new_skin = Some((skin, *variant)); } } @@ -532,8 +535,31 @@ impl Render for SkinsPage { this.bg(secondary) .hover(|style| style.bg(secondary_hover)) }) - .when(active, |this| { + .when_else(active, |this| { this.child(Icon::new(PandoraIcon::Flag).absolute().right(padding).bottom(padding)) + }, |this| { + this.child( + Button::new("delete-skin").icon(PandoraIcon::Trash2) + .danger() + .compact() + .absolute() + .small() + .left(padding) + .bottom(padding) + .on_click({ + let skin = skin.clone(); + let skin: Arc<[u8]> = skin.into(); + cx.listener(move |page, _, _, cx| { + page.data.backend_handle.send(MessageToBackend::RemoveFromSkinLibrary { + skin: { skin.clone() } + }); + cx.stop_propagation(); + if Arc::ptr_eq(&skin, &page.selected_skin) { + page.select_skin(page.active_skin.clone(), determine_skin_variant(&page.active_skin).unwrap_or(SkinVariant::Classic), cx); + } + }) + }) + ) }) .child(skin_img) .on_click({ @@ -548,7 +574,7 @@ impl Render for SkinsPage { }) })) }))); - + h_flex().p_4() .gap_4() .child(v_flex()