You've already forked AstralRinth
fix(skins): better offline handling (#6344)
This commit is contained in:
@@ -268,12 +268,11 @@ pub async fn get_available_capes() -> crate::Result<Vec<Cape>> {
|
||||
.await?
|
||||
.ok_or(ErrorKind::NoCredentialsError)?;
|
||||
|
||||
let profile = selected_credentials
|
||||
.online_profile_fresh()
|
||||
.await
|
||||
.ok_or_else(|| ErrorKind::OnlineMinecraftProfileUnavailable {
|
||||
user_name: selected_credentials.offline_profile.name.clone(),
|
||||
})?;
|
||||
let Some(profile) = selected_credentials.online_profile_fresh().await
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let pending_skin_change = pending_effective_skin_change(profile.id).await;
|
||||
let pending_cape_id = pending_skin_change
|
||||
.as_ref()
|
||||
@@ -309,16 +308,22 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
.await?
|
||||
.ok_or(ErrorKind::NoCredentialsError)?;
|
||||
|
||||
let profile = selected_credentials
|
||||
.online_profile_fresh()
|
||||
.await
|
||||
.ok_or_else(|| ErrorKind::OnlineMinecraftProfileUnavailable {
|
||||
user_name: selected_credentials.offline_profile.name.clone(),
|
||||
})?;
|
||||
let online_profile = selected_credentials.online_profile_fresh().await;
|
||||
let profile_id = online_profile
|
||||
.as_ref()
|
||||
.map_or(selected_credentials.offline_profile.id, |profile| {
|
||||
profile.id
|
||||
});
|
||||
|
||||
let current_skin = profile.current_skin()?;
|
||||
let current_cape_id = profile.current_cape().map(|cape| cape.id);
|
||||
let pending_skin_change = pending_effective_skin_change(profile.id).await;
|
||||
let current_skin = online_profile
|
||||
.as_ref()
|
||||
.map(|profile| profile.current_skin())
|
||||
.transpose()?;
|
||||
let current_cape_id = online_profile
|
||||
.as_ref()
|
||||
.and_then(|profile| profile.current_cape())
|
||||
.map(|cape| cape.id);
|
||||
let pending_skin_change = pending_effective_skin_change(profile_id).await;
|
||||
let pending_unequip = pending_skin_change
|
||||
.as_ref()
|
||||
.is_some_and(PendingEffectiveSkinChange::is_unequip);
|
||||
@@ -326,16 +331,15 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
.as_ref()
|
||||
.and_then(PendingEffectiveSkinChange::skin);
|
||||
|
||||
let fallback_default_skin = assets::DEFAULT_SKINS.first();
|
||||
let fallback_default_skin = get_fallback_default_skin()?;
|
||||
let current_skin_texture_key = pending_skin.as_ref().map_or_else(
|
||||
|| {
|
||||
if pending_unequip {
|
||||
fallback_default_skin.map_or_else(
|
||||
|| current_skin.texture_key(),
|
||||
|skin| Arc::clone(&skin.texture_key),
|
||||
)
|
||||
} else {
|
||||
Arc::clone(&fallback_default_skin.texture_key)
|
||||
} else if let Some(current_skin) = current_skin {
|
||||
current_skin.texture_key()
|
||||
} else {
|
||||
Arc::clone(&fallback_default_skin.texture_key)
|
||||
}
|
||||
},
|
||||
|skin| skin.texture_key.clone(),
|
||||
@@ -343,10 +347,11 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
let current_skin_variant = pending_skin.as_ref().map_or_else(
|
||||
|| {
|
||||
if pending_unequip {
|
||||
fallback_default_skin
|
||||
.map_or(current_skin.variant, |skin| skin.variant)
|
||||
} else {
|
||||
fallback_default_skin.variant
|
||||
} else if let Some(current_skin) = current_skin {
|
||||
current_skin.variant
|
||||
} else {
|
||||
fallback_default_skin.variant
|
||||
}
|
||||
},
|
||||
|skin| skin.variant,
|
||||
@@ -354,8 +359,10 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
let current_cape_id = pending_skin.as_ref().map_or(
|
||||
if pending_unequip {
|
||||
None
|
||||
} else {
|
||||
} else if current_skin.is_some() {
|
||||
current_cape_id
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|skin| skin.cape_id,
|
||||
);
|
||||
@@ -364,38 +371,39 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
let mut custom_skins = Vec::new();
|
||||
let mut saved_default_skins = Vec::new();
|
||||
|
||||
for mut custom_skin in CustomMinecraftSkin::get_all(profile.id, &state.pool)
|
||||
for mut custom_skin in CustomMinecraftSkin::get_all(profile_id, &state.pool)
|
||||
.await?
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
{
|
||||
let is_saved_default_skin =
|
||||
is_bundled_skin(&custom_skin.texture_key, custom_skin.variant);
|
||||
let current_skin_sync = if pending_skin.is_some() {
|
||||
SavedSkinSync {
|
||||
is_current_skin: custom_skin.texture_key
|
||||
== current_skin_texture_key.as_ref()
|
||||
&& custom_skin.variant == current_skin_variant
|
||||
&& custom_skin.cape_id == current_cape_id,
|
||||
settings_changed: false,
|
||||
}
|
||||
} else {
|
||||
sync_saved_skin_with_current_profile(
|
||||
&mut custom_skin,
|
||||
¤t_skin_texture_key,
|
||||
current_skin_variant,
|
||||
current_cape_id,
|
||||
)
|
||||
};
|
||||
let current_skin_sync =
|
||||
if pending_skin.is_some() || current_skin.is_none() {
|
||||
SavedSkinSync {
|
||||
is_current_skin: custom_skin.texture_key
|
||||
== current_skin_texture_key.as_ref()
|
||||
&& custom_skin.variant == current_skin_variant
|
||||
&& custom_skin.cape_id == current_cape_id,
|
||||
settings_changed: false,
|
||||
}
|
||||
} else {
|
||||
sync_saved_skin_with_current_profile(
|
||||
&mut custom_skin,
|
||||
¤t_skin_texture_key,
|
||||
current_skin_variant,
|
||||
current_cape_id,
|
||||
)
|
||||
};
|
||||
|
||||
let synced_texture_blob = if current_skin_sync.settings_changed {
|
||||
let texture_blob = custom_skin.texture_blob(&state.pool).await?;
|
||||
|
||||
if is_saved_default_skin && custom_skin.cape_id.is_none() {
|
||||
custom_skin.remove(profile.id, &state.pool).await?;
|
||||
custom_skin.remove(profile_id, &state.pool).await?;
|
||||
} else {
|
||||
CustomMinecraftSkin::add(
|
||||
profile.id,
|
||||
profile_id,
|
||||
&custom_skin.texture_key,
|
||||
&texture_blob,
|
||||
custom_skin.variant,
|
||||
@@ -483,7 +491,7 @@ pub async fn get_available_skins() -> crate::Result<Vec<Skin>> {
|
||||
if let Some(mut skin) = pending_skin {
|
||||
skin.is_equipped = true;
|
||||
available_skins.push(skin);
|
||||
} else {
|
||||
} else if let Some(current_skin) = current_skin {
|
||||
available_skins.push(Skin {
|
||||
texture_key: current_skin_texture_key,
|
||||
name: current_skin.name.as_deref().map(Arc::from),
|
||||
@@ -1247,6 +1255,25 @@ fn is_bundled_skin(texture_key: &str, variant: MinecraftSkinVariant) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_fallback_default_skin() -> crate::Result<&'static Skin> {
|
||||
assets::DEFAULT_SKINS
|
||||
.iter()
|
||||
.find(|skin| {
|
||||
skin.name.as_deref() == Some("Steve")
|
||||
&& skin.variant == MinecraftSkinVariant::Classic
|
||||
})
|
||||
.or_else(|| {
|
||||
assets::DEFAULT_SKINS
|
||||
.iter()
|
||||
.find(|skin| skin.name.as_deref() == Some("Steve"))
|
||||
})
|
||||
.or_else(|| assets::DEFAULT_SKINS.first())
|
||||
.ok_or_else(|| {
|
||||
ErrorKind::OtherError("No bundled default skins found".into())
|
||||
.as_error()
|
||||
})
|
||||
}
|
||||
|
||||
fn local_skin_texture_key(texture_blob: &[u8]) -> Arc<str> {
|
||||
Arc::from(format!("local-{:x}", sha2::Sha256::digest(texture_blob)))
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ const props = withDefaults(
|
||||
selected: boolean
|
||||
active?: boolean
|
||||
tooltip?: string
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
forwardImageSrc: undefined,
|
||||
backwardImageSrc: undefined,
|
||||
active: false,
|
||||
tooltip: undefined,
|
||||
disabled: false,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -52,13 +54,18 @@ watch(
|
||||
class="skin-button group relative flex items-end justify-center overflow-hidden border border-solid transition-[border-color,box-shadow] duration-200"
|
||||
:class="[
|
||||
selected ? 'skin-button--selected' : '',
|
||||
{ 'skin-button--with-actions': $slots['overlay-buttons'] },
|
||||
active ? 'skin-button--active' : '',
|
||||
{
|
||||
'skin-button--with-actions': $slots['overlay-buttons'] && !disabled,
|
||||
'skin-button--disabled': disabled,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<button
|
||||
class="absolute inset-0 z-10 cursor-pointer border-none bg-transparent p-0 focus-visible:outline-none"
|
||||
:aria-label="tooltip ? `Select ${tooltip}` : 'Select skin'"
|
||||
:aria-pressed="selected"
|
||||
:disabled="disabled"
|
||||
@click="emit('select')"
|
||||
></button>
|
||||
|
||||
@@ -98,7 +105,7 @@ watch(
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="$slots['overlay-buttons']"
|
||||
v-if="$slots['overlay-buttons'] && !disabled"
|
||||
class="pointer-events-none absolute inset-x-0 bottom-3 z-30 flex translate-y-2 items-center justify-start gap-1.5 px-3 opacity-0 transition-all duration-200 group-focus-within:translate-y-0 group-focus-within:opacity-100 group-hover:translate-y-0 group-hover:opacity-100"
|
||||
>
|
||||
<slot name="overlay-buttons" />
|
||||
@@ -156,8 +163,8 @@ watch(
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.skin-button:hover,
|
||||
.skin-button:focus-within,
|
||||
.skin-button:not(.skin-button--disabled):hover,
|
||||
.skin-button:not(.skin-button--disabled):focus-within,
|
||||
.skin-button--with-actions:hover,
|
||||
.skin-button--with-actions:focus-within {
|
||||
border-color: var(--surface-5);
|
||||
@@ -167,13 +174,27 @@ watch(
|
||||
0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.skin-button--selected,
|
||||
.skin-button--selected:hover,
|
||||
.skin-button--selected:focus-within {
|
||||
.skin-button.skin-button--selected,
|
||||
.skin-button.skin-button--selected:hover,
|
||||
.skin-button.skin-button--selected:focus-within,
|
||||
.skin-button.skin-button--selected.skin-button--with-actions:hover,
|
||||
.skin-button.skin-button--selected.skin-button--with-actions:focus-within,
|
||||
.skin-button.skin-button--active:hover,
|
||||
.skin-button.skin-button--active:focus-within,
|
||||
.skin-button.skin-button--active.skin-button--with-actions:hover,
|
||||
.skin-button.skin-button--active.skin-button--with-actions:focus-within {
|
||||
border-color: var(--color-brand);
|
||||
background: var(--color-brand-highlight);
|
||||
}
|
||||
|
||||
.skin-button--disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.skin-button--disabled button {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.skin-button__image-parent {
|
||||
width: 100%;
|
||||
height: 95%;
|
||||
@@ -185,7 +206,7 @@ watch(
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.skin-button:hover .skin-button__image-parent {
|
||||
.skin-button:not(.skin-button--disabled):hover .skin-button__image-parent {
|
||||
transform: rotateY(180deg) translateZ(0);
|
||||
}
|
||||
|
||||
@@ -208,7 +229,7 @@ watch(
|
||||
transition: filter 200ms ease-in-out;
|
||||
}
|
||||
|
||||
.group:hover .skin-button__image-parent img {
|
||||
.group:not(.skin-button--disabled):hover .skin-button__image-parent img {
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,12 +7,14 @@ const props = withDefaults(
|
||||
tooltip?: string
|
||||
dragActive?: boolean
|
||||
dropzone?: boolean
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
selected: false,
|
||||
tooltip: undefined,
|
||||
dragActive: false,
|
||||
dropzone: false,
|
||||
disabled: false,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -28,6 +30,10 @@ function handleDragEvent(
|
||||
eventName: 'dragenter' | 'dragover' | 'dragleave' | 'drop',
|
||||
event: DragEvent,
|
||||
) {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.dropzone) {
|
||||
event.preventDefault()
|
||||
if (event.dataTransfer) {
|
||||
@@ -53,7 +59,10 @@ defineExpose({ getRootElement })
|
||||
:class="[
|
||||
isHighlighted
|
||||
? 'border-brand bg-brand-highlight'
|
||||
: 'border-surface-5 bg-surface-2 hover:bg-surface-3',
|
||||
: disabled
|
||||
? 'border-surface-5 bg-surface-2'
|
||||
: 'border-surface-5 bg-surface-2 hover:bg-surface-3',
|
||||
disabled ? 'opacity-[0.65]' : '',
|
||||
]"
|
||||
@dragenter="handleDragEvent('dragenter', $event)"
|
||||
@dragover="handleDragEvent('dragover', $event)"
|
||||
@@ -64,6 +73,8 @@ defineExpose({ getRootElement })
|
||||
type="button"
|
||||
:aria-label="tooltip ?? undefined"
|
||||
class="absolute inset-0 z-0 cursor-pointer border-none bg-transparent p-0"
|
||||
:class="{ 'cursor-not-allowed': disabled }"
|
||||
:disabled="disabled"
|
||||
@click="(e) => emit('click', e)"
|
||||
></button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user