You've already forked AstralRinth
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:
@@ -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?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures::{Stream, StreamExt, stream};
|
||||
use uuid::{Uuid, fmt::Hyphenated};
|
||||
|
||||
@@ -22,12 +24,22 @@ pub struct CustomMinecraftSkin {
|
||||
///
|
||||
/// If `None`, the skin is saved without a cape.
|
||||
pub cape_id: Option<Uuid>,
|
||||
/// The saved skin display order within this player's saved skins.
|
||||
pub display_order: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CustomMinecraftSkinInsertPosition {
|
||||
Top,
|
||||
Bottom,
|
||||
At(i64),
|
||||
}
|
||||
|
||||
struct CustomMinecraftSkinRow {
|
||||
texture_key: String,
|
||||
variant: MinecraftSkinVariant,
|
||||
cape_id: Option<Hyphenated>,
|
||||
display_order: i64,
|
||||
}
|
||||
|
||||
impl CustomMinecraftSkin {
|
||||
@@ -37,6 +49,7 @@ impl CustomMinecraftSkin {
|
||||
texture: &[u8],
|
||||
variant: MinecraftSkinVariant,
|
||||
cape_id: Option<Uuid>,
|
||||
insert_position: CustomMinecraftSkinInsertPosition,
|
||||
db: impl sqlx::Acquire<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
let minecraft_user_id = minecraft_user_id.as_hyphenated();
|
||||
@@ -44,6 +57,51 @@ impl CustomMinecraftSkin {
|
||||
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
let existing_order = sqlx::query_scalar!(
|
||||
"SELECT display_order FROM custom_minecraft_skins WHERE minecraft_user_uuid = ? AND texture_key = ?",
|
||||
minecraft_user_id,
|
||||
texture_key
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let display_order = match existing_order {
|
||||
Some(display_order) => display_order,
|
||||
None => match insert_position {
|
||||
CustomMinecraftSkinInsertPosition::Top => {
|
||||
sqlx::query!(
|
||||
"UPDATE custom_minecraft_skins SET display_order = display_order + 1 WHERE minecraft_user_uuid = ?",
|
||||
minecraft_user_id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
0
|
||||
}
|
||||
CustomMinecraftSkinInsertPosition::Bottom => {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COALESCE(MAX(display_order) + 1, 0) AS 'display_order!: i64' \
|
||||
FROM custom_minecraft_skins WHERE minecraft_user_uuid = ?",
|
||||
minecraft_user_id
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?
|
||||
}
|
||||
CustomMinecraftSkinInsertPosition::At(display_order) => {
|
||||
sqlx::query!(
|
||||
"UPDATE custom_minecraft_skins SET display_order = display_order + 1 \
|
||||
WHERE minecraft_user_uuid = ? AND display_order >= ?",
|
||||
minecraft_user_id,
|
||||
display_order
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
display_order
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM custom_minecraft_skins WHERE minecraft_user_uuid = ? AND texture_key = ?",
|
||||
minecraft_user_id,
|
||||
@@ -57,11 +115,11 @@ impl CustomMinecraftSkin {
|
||||
texture_key, texture
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT OR REPLACE INTO custom_minecraft_skins (minecraft_user_uuid, texture_key, variant, cape_id) VALUES (?, ?, ?, ?)",
|
||||
minecraft_user_id, texture_key, variant, cape_id
|
||||
"INSERT INTO custom_minecraft_skins (minecraft_user_uuid, texture_key, variant, cape_id, display_order) VALUES (?, ?, ?, ?, ?)",
|
||||
minecraft_user_id, texture_key, variant, cape_id, display_order
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -80,7 +138,7 @@ impl CustomMinecraftSkin {
|
||||
|
||||
sqlx::query_as!(
|
||||
CustomMinecraftSkinRow,
|
||||
"SELECT texture_key, variant AS 'variant: MinecraftSkinVariant', cape_id AS 'cape_id: Hyphenated' \
|
||||
"SELECT texture_key, variant AS 'variant: MinecraftSkinVariant', cape_id AS 'cape_id: Hyphenated', display_order \
|
||||
FROM custom_minecraft_skins \
|
||||
WHERE minecraft_user_uuid = ? AND texture_key = ?",
|
||||
minecraft_user_id,
|
||||
@@ -93,6 +151,7 @@ impl CustomMinecraftSkin {
|
||||
texture_key: row.texture_key,
|
||||
variant: row.variant,
|
||||
cape_id: row.cape_id.map(Uuid::from),
|
||||
display_order: row.display_order,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
@@ -107,10 +166,10 @@ impl CustomMinecraftSkin {
|
||||
let minecraft_user_id = minecraft_user_id.as_hyphenated();
|
||||
|
||||
Ok(stream::iter(sqlx::query!(
|
||||
"SELECT texture_key, variant AS 'variant: MinecraftSkinVariant', cape_id AS 'cape_id: Hyphenated' \
|
||||
"SELECT texture_key, variant AS 'variant: MinecraftSkinVariant', cape_id AS 'cape_id: Hyphenated', display_order \
|
||||
FROM custom_minecraft_skins \
|
||||
WHERE minecraft_user_uuid = ? \
|
||||
ORDER BY rowid ASC \
|
||||
ORDER BY display_order ASC, rowid ASC \
|
||||
LIMIT ? OFFSET ?",
|
||||
minecraft_user_id, count, offset
|
||||
)
|
||||
@@ -120,6 +179,7 @@ impl CustomMinecraftSkin {
|
||||
texture_key: row.texture_key,
|
||||
variant: row.variant,
|
||||
cape_id: row.cape_id.map(Uuid::from),
|
||||
display_order: row.display_order,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -161,4 +221,62 @@ impl CustomMinecraftSkin {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_order(
|
||||
minecraft_user_id: Uuid,
|
||||
texture_keys: &[String],
|
||||
db: impl sqlx::Acquire<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
let minecraft_user_id = minecraft_user_id.as_hyphenated();
|
||||
let mut transaction = db.begin().await?;
|
||||
|
||||
let existing_rows = sqlx::query!(
|
||||
"SELECT texture_key FROM custom_minecraft_skins \
|
||||
WHERE minecraft_user_uuid = ? \
|
||||
ORDER BY display_order ASC, rowid ASC",
|
||||
minecraft_user_id
|
||||
)
|
||||
.fetch_all(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let existing_keys = existing_rows
|
||||
.iter()
|
||||
.map(|row| row.texture_key.as_str())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut seen_keys = HashSet::new();
|
||||
let mut ordered_keys = Vec::with_capacity(existing_rows.len());
|
||||
|
||||
for texture_key in texture_keys {
|
||||
if seen_keys.insert(texture_key.as_str())
|
||||
&& existing_keys.contains(texture_key.as_str())
|
||||
{
|
||||
ordered_keys.push(texture_key.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for row in &existing_rows {
|
||||
if seen_keys.insert(row.texture_key.as_str()) {
|
||||
ordered_keys.push(row.texture_key.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (display_order, texture_key) in ordered_keys.into_iter().enumerate()
|
||||
{
|
||||
let display_order = display_order as i64;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE custom_minecraft_skins SET display_order = ? \
|
||||
WHERE minecraft_user_uuid = ? AND texture_key = ?",
|
||||
display_order,
|
||||
minecraft_user_id,
|
||||
texture_key
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user