polish: use figma values

This commit is contained in:
Calum H.
2025-05-22 12:56:21 +01:00
committed by Alejandro González
parent 721577cdb1
commit cf354d2bbc
3 changed files with 116 additions and 65 deletions

View File

@@ -8,7 +8,7 @@ import {
Button
} from '@modrinth/ui'
import type { Ref} from 'vue';
import {ref, computed, useTemplateRef, watch, onMounted, onUnmounted, inject} from 'vue'
import {ref, computed, useTemplateRef, watch, onMounted, onUnmounted, inject, nextTick} from 'vue'
import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
import { handleError } from '@/store/notifications'
@@ -41,7 +41,6 @@ const loginInProgress = ref(false);
const currentUser = ref(undefined)
const currentUserId = ref<string | undefined>(undefined)
// @ts-ignore
const username = computed(() => currentUser.value?.profile?.name ?? undefined)
const selectedSkin = ref<Skin | null>(null)
const defaultCape = ref<Cape>()
@@ -64,36 +63,8 @@ const capeTexture = computed(() => currentCape.value?.texture)
const skinVariant = computed(() => selectedSkin.value?.variant)
const skinNametag = computed(() => settings.value.hide_nametag_skins_page ? undefined : username.value)
await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
let userCheckInterval: number | null = null
onMounted(() => {
// TODO: Surely a better way to do this?
userCheckInterval = window.setInterval(checkUserChanges, 250)
})
onUnmounted(() => {
if (userCheckInterval !== null) {
window.clearInterval(userCheckInterval)
}
})
async function checkUserChanges() {
try {
const defaultId = await get_default_user()
if (defaultId !== currentUserId.value) {
await loadCurrentUser();
await loadCapes();
await loadSkins();
}
} catch (error) {
if (currentUser.value) {
handleError(error)
}
}
}
async function loadCapes() {
try {
capes.value = (await get_available_capes()) ?? []
@@ -138,7 +109,6 @@ async function loadCurrentUser() {
currentUserId.value = defaultId
const allAccounts = await users()
// @ts-ignore
currentUser.value = allAccounts.find((acc) => acc.profile.id === defaultId)
} catch (e) {
handleError(e)
@@ -168,6 +138,33 @@ watch(
() => selectedSkin.value?.cape_id,
() => {}
)
onMounted(() => {
userCheckInterval = window.setInterval(checkUserChanges, 250);
})
onUnmounted(() => {
if (userCheckInterval !== null) {
window.clearInterval(userCheckInterval);
}
})
async function checkUserChanges() {
try {
const defaultId = await get_default_user()
if (defaultId !== currentUserId.value) {
await loadCurrentUser();
await loadCapes();
await loadSkins();
}
} catch (error) {
if (currentUser.value) {
handleError(error)
}
}
}
await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
</script>
<template>
@@ -179,8 +176,8 @@ watch(
/>
<SelectCapeModal ref="selectCapeModal" :capes="capes" @select="handleCapeSelected" />
<div v-if="currentUser" class="p-4 grid grid-cols-[300px_1fr] xl:grid-cols-[3fr_5fr] gap-6">
<div class="sticky top-6 self-start p-2">
<div v-if="currentUser" class="p-4 skin-layout">
<div class="preview-panel">
<div class="flex justify-between gap-4">
<h1 class="m-0 text-2xl font-bold">Skins</h1>
<ButtonStyled :disabled="!!selectedSkin?.cape_id">
@@ -206,7 +203,7 @@ watch(
</button>
</ButtonStyled>
</div>
<div class="h-[80vh] flex items-center justify-center">
<div class="preview-container">
<SkinPreviewRenderer
wide-model-src="/src/assets/models/classic_player.gltf"
slim-model-src="/src/assets/models/slim_player.gltf"
@@ -220,25 +217,25 @@ watch(
</div>
</div>
<div class="flex flex-col gap-6 add-perspective pt-2">
<div class="skins-container">
<section class="flex flex-col gap-2 mt-1">
<h2 class="text-lg font-bold m-0 text-primary">Saved skins</h2>
<div class="flex flex-row flex-wrap gap-2 w-full">
<div class="skin-card-grid">
<SkinLikeTextButton
class="flex-none skin-card"
tooltip="Add a skin"
@click="editSkinModal?.show"
>
<template #icon>
<PlusIcon />
</template>
Add a skin
class="skin-card"
tooltip="Add a skin"
@click="editSkinModal?.show"
>
<template #icon>
<PlusIcon class="w-5 h-5 stroke-2" />
</template>
<span>Add a skin</span>
</SkinLikeTextButton>
<SkinButton
v-for="skin in savedSkins"
:key="`saved-skin-${skin.texture_key}`"
class="flex-none skin-card"
class="skin-card"
editable
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
:backward-image-src="getBakedSkinTextures(skin)?.backwards"
@@ -249,13 +246,13 @@ watch(
</div>
</section>
<section class="flex flex-col gap-2 mt-1 mr-0">
<section class="flex flex-col gap-2 mt-6">
<h2 class="text-lg font-bold m-0 text-primary">Default skins</h2>
<div class="flex flex-row flex-wrap gap-2 w-full">
<div class="skin-card-grid">
<SkinButton
v-for="skin in defaultSkins"
:key="`default-skin-${skin.texture_key}`"
class="flex-none skin-card"
class="skin-card"
:forward-image-src="getBakedSkinTextures(skin)?.forwards"
:backward-image-src="getBakedSkinTextures(skin)?.backwards"
:selected="selectedSkin === skin"
@@ -291,15 +288,56 @@ watch(
</template>
<style lang="scss" scoped>
$skin-card-width: 10.5vw;
$skin-card-aspect: 1.1;
$skin-card-width: 140px;
$skin-card-gap: 4px;
.skin-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
gap: 2.5rem;
@media (max-width: 700px) {
grid-template-columns: 1fr;
}
}
.preview-panel {
position: sticky;
top: 1.5rem;
align-self: start;
padding: 0.5rem;
@media (max-width: 700px) {
position: static;
}
}
.preview-container {
height: 80vh;
display: flex;
align-items: center;
justify-content: center;
@media (max-width: 700px) {
height: 50vh;
}
}
.skins-container {
padding-top: 0.5rem;
}
.skin-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, $skin-card-width);
gap: $skin-card-gap;
width: 100%;
}
.skin-card {
width: $skin-card-width;
min-width: $skin-card-width;
max-width: $skin-card-width;
height: calc(#{$skin-card-width} * #{$skin-card-aspect});
min-height: calc(#{$skin-card-width} * #{$skin-card-aspect});
max-height: calc(#{$skin-card-width} * #{$skin-card-aspect});
aspect-ratio: 1.05;
border-radius: 10px;
box-sizing: border-box;
}
</style>

View File

@@ -41,30 +41,30 @@ function onImageLoad(type: 'forward' | 'backward') {
]"
>
<button
class="skin-btn-bg absolute inset-0 rounded-xl cursor-pointer p-0 border-none group-hover:brightness-125"
class="skin-btn-bg absolute inset-0 cursor-pointer p-0 border-none group-hover:brightness-125"
:class="selected ? 'selected' : ''"
@click="emit('select')"
></button>
<div v-if="!(imagesLoaded.forward && imagesLoaded.backward)" class="skeleton-loader w-full h-full rounded-xl">
<div class="skeleton absolute inset-0 rounded-xl aspect-[5/7]"></div>
<div v-if="!(imagesLoaded.forward && imagesLoaded.backward)" class="skeleton-loader w-full h-full">
<div class="skeleton absolute inset-0 aspect-[5/7]"></div>
</div>
<span
<span
v-show="imagesLoaded.forward && imagesLoaded.backward"
:class="['skin-button__image-parent pointer-events-none w-full h-full grid [transform-style:preserve-3d] transition-transform duration-500 group-hover:[transform:rotateY(180deg)] place-items-stretch', selected ? 'with-shadow' : '']"
>
<img
alt=""
:src="forwardImageSrc"
class="skin-button__image-facing rounded-xl object-contain w-full h-full [backface-visibility:hidden] col-start-1 row-start-1"
class="skin-button__image-facing object-contain w-full h-full [backface-visibility:hidden] col-start-1 row-start-1"
height="504"
@load="onImageLoad('forward')"
/>
<img
alt=""
:src="backwardImageSrc"
class="skin-button__image-away rounded-xl object-contain w-full h-full [backface-visibility:hidden] [transform:rotateY(180deg)] col-start-1 row-start-1"
class="skin-button__image-away object-contain w-full h-full [backface-visibility:hidden] [transform:rotateY(180deg)] col-start-1 row-start-1"
height="504"
@load="onImageLoad('backward')"
/>
@@ -118,8 +118,14 @@ function onImageLoad(type: 'forward' | 'backward') {
background: linear-gradient(180deg, #3a3d47 0%, #33363d 100%);
}
.skin-btn-bg.selected {
background: linear-gradient(180deg, #5a5d6b 0%, #424956 100%);
background: linear-gradient(157.61deg, var(--color-brand) -76.68%, rgba(27, 217, 106, 0.534) -38.61%, rgba(12, 89, 44, 0.6) 100.4%), #27292F;
}
.skin-btn-bg.selected:hover,
.group:hover .skin-btn-bg.selected {
filter: brightness(1.15);
}
.with-shadow img {
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.4));
}

View File

@@ -5,7 +5,7 @@
Drag to rotate
</span>
</div>
<div v-if="nametag" class="absolute top-[13%] left-1/2 transform -translate-x-1/2 px-3 py-1 rounded-md text-[200%] pointer-events-none z-10 font-minecraft text-white bg-black/60 shadow-md border-solid border-1 border-white/10">
<div v-if="nametag" class="absolute top-[13%] left-1/2 transform -translate-x-1/2 px-3 py-1 rounded-md text-[200%] pointer-events-none z-10 font-minecraft text-primary nametag-bg">
{{ nametag }}
</div>
@@ -226,7 +226,7 @@ function applyTextureToScene(root: THREE.Object3D | null, tex: THREE.Texture | n
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
materials.forEach((mat, index, array) => {
materials.forEach((mat, _index, _array) => {
const standardMat = mat as THREE.MeshStandardMaterial
standardMat.map = tex
standardMat.metalness = 0
@@ -248,7 +248,7 @@ function applyCapeTexture(root: THREE.Object3D | null, tex: THREE.Texture | null
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
materials.forEach((mat, index, array) => {
materials.forEach((mat, _index, _array) => {
const standardMat = mat as THREE.MeshStandardMaterial
standardMat.map = tex || transparentTexture
standardMat.transparent = true
@@ -342,3 +342,10 @@ onBeforeMount(async () => {
}
})
</script>
<style scoped lang="scss">
.nametag-bg {
background: linear-gradient(308.68deg, rgba(0, 0, 0, 0) -52.46%, rgba(100, 100, 100, 0.1) 94.75%), rgba(0, 0, 0, 0.2);
box-shadow: inset -0.5px -0.5px 0px rgba(0, 0, 0, 0.25), inset 0.5px 0.5px 0px rgba(255, 255, 255, 0.05);
}
</style>