feat: drag and drop skins to reorder (#6357)

* feat: drag and drop skins to reorder

* feat: implement drag to reorder skins

* fix: ci

* remove: backend implementation

* regenerate sqlx

* fix: remove v-if selectable

* feat: remove drag handle

* refactor: pnpm prepr

* cargo fmt

* fix: dragging disable hover, wrong evt for edit skin + remove back of skin hover

---------

Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
Truman Gao
2026-06-11 12:22:38 +00:00
committed by GitHub
co-authored by Calum H.
parent d2a66bb2b0
commit c1780eef7d
29 changed files with 713 additions and 162 deletions
+80 -2
View File
@@ -65,7 +65,9 @@ use crate::{
ErrorKind, State,
state::{
MinecraftCharacterExpressionState, MinecraftProfile,
minecraft_skins::{CustomMinecraftSkin, mojang_api},
minecraft_skins::{
CustomMinecraftSkin, CustomMinecraftSkinInsertPosition, mojang_api,
},
},
};
@@ -408,6 +410,7 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
&texture_blob,
custom_skin.variant,
custom_skin.cape_id,
CustomMinecraftSkinInsertPosition::Bottom,
&state.pool,
)
.await?;
@@ -453,7 +456,6 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
});
}
custom_skins.sort_by(|a, b| a.texture.as_str().cmp(b.texture.as_str()));
available_skins.extend(custom_skins);
for default_skin in assets::DEFAULT_SKINS.iter() {
@@ -534,6 +536,7 @@ pub async fn add_and_equip_custom_skin(
&texture_blob,
variant,
cape_id,
CustomMinecraftSkinInsertPosition::Top,
&state.pool,
)
.await?;
@@ -606,6 +609,21 @@ async fn add_and_equip_custom_skin_now(
let equipped_skin = profile.current_skin()?;
let equipped_skin_texture_key = equipped_skin.texture_key();
let equipped_skin_variant = equipped_skin.variant;
let insert_position = if local_texture_key
!= equipped_skin_texture_key.as_ref()
{
CustomMinecraftSkin::get_by_texture(
profile.id,
local_texture_key,
&state.pool,
)
.await?
.map_or(CustomMinecraftSkinInsertPosition::Top, |skin| {
CustomMinecraftSkinInsertPosition::At(skin.display_order)
})
} else {
CustomMinecraftSkinInsertPosition::Top
};
let persistence_result = if cape_id.is_none()
&& is_bundled_skin(&equipped_skin_texture_key, equipped_skin_variant)
@@ -614,6 +632,7 @@ async fn add_and_equip_custom_skin_now(
texture_key: equipped_skin_texture_key.to_string(),
variant: equipped_skin_variant,
cape_id: None,
display_order: 0,
}
.remove(profile.id, &state.pool)
.await
@@ -624,6 +643,7 @@ async fn add_and_equip_custom_skin_now(
&texture_blob,
variant,
cape_id,
insert_position,
&state.pool,
)
.await
@@ -639,6 +659,7 @@ async fn add_and_equip_custom_skin_now(
texture_key: local_texture_key.to_string(),
variant,
cape_id,
display_order: 0,
}
.remove(profile.id, &state.pool)
.await?;
@@ -741,6 +762,22 @@ async fn persist_equipped_skin(
let equipped_skin_variant = equipped_skin.variant;
let texture_key_changed =
skin.texture_key.as_ref() != equipped_skin_texture_key.as_ref();
let insert_position = if texture_key_changed {
CustomMinecraftSkin::get_by_texture(
profile.id,
&skin.texture_key,
&state.pool,
)
.await?
.map_or(
CustomMinecraftSkinInsertPosition::Bottom,
|saved_skin| {
CustomMinecraftSkinInsertPosition::At(saved_skin.display_order)
},
)
} else {
CustomMinecraftSkinInsertPosition::Bottom
};
if skin.cape_id.is_none()
&& is_bundled_skin(&equipped_skin_texture_key, equipped_skin_variant)
@@ -749,6 +786,7 @@ async fn persist_equipped_skin(
texture_key: equipped_skin_texture_key.to_string(),
variant: equipped_skin_variant,
cape_id: None,
display_order: 0,
}
.remove(profile.id, &state.pool)
.await?;
@@ -760,6 +798,7 @@ async fn persist_equipped_skin(
texture_blob,
equipped_skin_variant,
skin.cape_id,
insert_position,
&state.pool,
)
.await?;
@@ -770,6 +809,7 @@ async fn persist_equipped_skin(
texture_key: skin.texture_key.to_string(),
variant: skin.variant,
cape_id: skin.cape_id,
display_order: 0,
}
.remove(profile.id, &state.pool)
.await?;
@@ -796,6 +836,7 @@ pub async fn remove_custom_skin(skin: Skin) -> crate::Result<()> {
texture_key: skin.texture_key.to_string(),
variant: skin.variant,
cape_id: skin.cape_id,
display_order: 0,
}
.remove(selected_credentials.offline_profile.id, &state.pool)
.await?;
@@ -809,6 +850,25 @@ pub async fn remove_custom_skin(skin: Skin) -> crate::Result<()> {
Ok(())
}
/// Persists the custom saved skin order for the currently selected Minecraft profile.
#[tracing::instrument]
pub async fn set_custom_skin_order(
texture_keys: Vec<String>,
) -> crate::Result<()> {
let state = State::get().await?;
let selected_credentials = Credentials::get_default_credential(&state.pool)
.await?
.ok_or(ErrorKind::NoCredentialsError)?;
CustomMinecraftSkin::set_order(
selected_credentials.offline_profile.id,
&texture_keys,
&state.pool,
)
.await
}
/// Adds or updates a saved skin locally without applying it to Mojang.
///
/// This is used by the skin editor. If the edited skin is currently equipped, the caller should
@@ -839,12 +899,26 @@ pub async fn save_custom_skin(
Arc::clone(&skin.texture_key)
};
let cape_id = cape.map(|cape| cape.id);
let insert_position = if replace_texture && old_texture_key != texture_key {
CustomMinecraftSkin::get_by_texture(
selected_credentials.offline_profile.id,
&old_texture_key,
&state.pool,
)
.await?
.map_or(CustomMinecraftSkinInsertPosition::Bottom, |skin| {
CustomMinecraftSkinInsertPosition::At(skin.display_order)
})
} else {
CustomMinecraftSkinInsertPosition::Bottom
};
if cape_id.is_none() && is_bundled_skin(&texture_key, variant) {
CustomMinecraftSkin {
texture_key: texture_key.to_string(),
variant,
cape_id: None,
display_order: 0,
}
.remove(selected_credentials.offline_profile.id, &state.pool)
.await?;
@@ -855,6 +929,7 @@ pub async fn save_custom_skin(
&texture_blob,
variant,
cape_id,
insert_position,
&state.pool,
)
.await?;
@@ -865,6 +940,7 @@ pub async fn save_custom_skin(
texture_key: old_texture_key.to_string(),
variant: skin.variant,
cape_id: skin.cape_id,
display_order: 0,
}
.remove(selected_credentials.offline_profile.id, &state.pool)
.await?;
@@ -1210,6 +1286,7 @@ async fn preserve_current_profile_skin(
&texture,
current_skin.variant,
current_cape_id,
CustomMinecraftSkinInsertPosition::Bottom,
&state.pool,
)
.await?;
@@ -1231,6 +1308,7 @@ async fn preserve_current_profile_skin(
&texture,
current_skin.variant,
current_cape_id,
CustomMinecraftSkinInsertPosition::Bottom,
&state.pool,
)
.await?;