From d8cb19bf404cd9c4c9ee02d6810d92a68a743ffc Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Thu, 15 May 2025 15:25:24 +0100 Subject: [PATCH] feat: Finish new skin modal --- .../src/assets/skins/herobrine.png | Bin 0 -> 2159 bytes apps/app-frontend/src/assets/skins/steve.png | Bin 0 -> 1311 bytes .../src/components/ui/skin/EditSkinModal.vue | 93 +++++++++++++----- .../components/ui/skin/SelectCapeModal.vue | 2 +- apps/app-frontend/src/helpers/skins.ts | 8 +- apps/app-frontend/src/pages/Skins.vue | 34 ++++++- packages/ui/src/components/index.ts | 2 + .../components/skin/SkinPreviewRenderer.vue | 2 +- 8 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 apps/app-frontend/src/assets/skins/herobrine.png create mode 100644 apps/app-frontend/src/assets/skins/steve.png diff --git a/apps/app-frontend/src/assets/skins/herobrine.png b/apps/app-frontend/src/assets/skins/herobrine.png new file mode 100644 index 0000000000000000000000000000000000000000..2d964e9acf66b0382aff0bf0f020d8db6e825434 GIT binary patch literal 2159 zcmV-#2$1)QP)Px-DM>^@RCr$PT3u`uRTREUy4$6@+eJ`*3hIL;Sj2*8Q$P}_fd|{DX$UnbHSwW+ zF!2Gy1Br<-#KeRLOdpIyt1&U9#2VxUG^t=MMM(iGn4n|X5`YnZt33P0Fa-$d+ z8nHEENpVo@Inw0_@~mJ0=uXrp`lJ71MfuzZfGlL^3bpIkibyOb>|7x1^UZz+JYIT_ z41zoW;v8;)_HDlc90DuQQ?gmh}VauapC>j^V0k7?GP6R=o z_IN-907!#SzT@l<3IjHkm&x(GX8_OhMD>YSTon4eO3nuhc%w8(V{{J(i^mRfAuflp z%fAih)I~g$=lKLN@o;dVoPT6w%sM9!pgg$+9U+Itf#Y$5))4@Jo{zDvAOFlP0J@K3 za{6Lt&yKBP`O^DDK~YE~Mu)}NL|jB7gW|o@J;E>yWnqN-$xV2=FTMawt6);9xxCb=H{@u4hVV!k_qp0_DB~ZH=w{rZpHgK z2Vnos%?4I&Xrw$8;UGZ4u5*_}q2DJa#$)nGdEOH@0it^d1j!J#Iy`*#lERkVo7c;J zt`|xv5qQgE2jtS1+HK!wNxDbm@yeAenWOfF3r2g}LFpb~0|ARbT35eUY-u{B0HJrj z{eEBO=N~&ZIlpLBv~(VpL7H0K+echw6-NMWjeR7a9S(=(v8SiU5rCFMuUpE~#K5km zm*1Rm0NQ&`Sn&veyZ!uj1Yjx*$Tk2)p^z9I9u^`R6$C(DF(9ih(AsKpzwf> z|3-8p6cQ2uC^Il1+I}@xiS-X{RmSb)tyx1!UGb!~Eg60t09?OrURt(H2mnApLGEq= zfbwWu!Fb$^;+z3G0G-eMF76BsNx22Z#Uj3Hm8n2d5%1Nj%9yvmUyRC%$6;kC0pJtTgPaopdKN&0kFQxHA8AoK`2tFFJPY8GWp&B`$ta!< z0Gcl;%3Y#lg_(t@IWmCO{o+F^IVQwot_I> z$h_Ovl|p+R0O|t>*8o&kS4##A{_&K8oZeZqqRaMrM@@|~u7hEv#Uvy2hsdDE7(&1i zOAg;KzaqKc^JnP)BwWXU%F0T^@Ar!U%dt3*@>Q2ybwXLSBb_=$87=7;{bu82T4a40049|ltUvh2m~n3 z8_8k3=uTtWr83-zvI>+$+j?|~?ZS#;4Zwm7#iCthtyU1^!OhP9dHN$D9MfP%-v19S zy83xA7x9<}U@ltKjZX6naD&!dl$~clzFW2E{N`OC+X(0T^XD0m?-t7T{N@>uZG`jv`ST2zX$$G~1+KT(`R1n(qP@G_nj(IJ zIQ`&otIv!xVA=&18UW4h&2lq@{`EQi;>tS!rpf@S0pJXPaqBM3;4c5y4_{SbXNDPtfl>ZyIKG{emiV`JGsQPV}K3-UZ`)er>j4JY{!nD?YFma zF&zjl_W%EL0H7@X=Tp;b4D_ux9e{$ROH->uxWfD#FPRMtz&v>g4&Qz5DDPH{r0?%L znQ*mPylFM^8#Y)*5 lxdR149uw^91E%|V{13r5rs)UV`o91G002ovPDHLkV1i4q_*MV_ literal 0 HcmV?d00001 diff --git a/apps/app-frontend/src/assets/skins/steve.png b/apps/app-frontend/src/assets/skins/steve.png new file mode 100644 index 0000000000000000000000000000000000000000..f97afdd5a38db604a1608872d48638a6d052b230 GIT binary patch literal 1311 zcmV+)1>pLLP)4e-7!_2IfS`gPf>wzHZIl>-f;mkgBz8)QG(oTl+F2;th}gtR3sa>%8=DjsHu(wp zPiNld-ty+nx3{^(?A^tE@R-?|Z};ZC`DS+6y)$QOaOdX59ym9BxhPrZ@#hy02GXxL zaiHMN?W?7D)ZKu0&*l=IDg#uom*3YqnRpU?e!Yo%Bi; z-W27pw`)b){h2FSEXZTFX`Lpp&BxO?I3OLU&Lo6FFoaV)iM~iwbWPS7&to4?x=a0UYqEQEMj2N`xoakCKG%{!`4DcDEASGw~_2at*H3O6sbfw|gLd9Ocu8KTEQWC8_=Awzd-L%wSNZ`2N7F;=!a{g^J*kZ2R;nrJqv= z3OuX<+7Du|=QlSCmV)|1nU>xj#U~=YjqNTTgy@Zp1JVGEtHU6Ho*mu~Lde;V9}A*h z{&2taHjj4`+^kFx**(aZ&;UKB4ub?%8K8BFA?pNrd?JjZRD`Fvlj79`WgT1~kE1s2 zBVAB3<7R-)eq=zl_PYn09LWt;%ZPf8>&@T>_zXve=&`4CX+U3y8{qMtA*J~KvEJfz z$JaI$t?|X?TZhO1Uj~EvDT?$I)>-w~DBjPFM5z&x0a|yx87QnuV4VRNq>3prKxdTM z&==(Kp#c&f8K6d}0VqAj^=9z&R^dvIBPmG5QMij^jK?Jgh4thzWFphkF%>&>vT zveE;6b8|AzsiGn?RG`O}28d`C52{!f7n$Jwt~Uc7Z#$h%B5HMYH4};$k40UVh#IDu zLnA26V|4jw3^K9G0L=_HKx18R20r6coC~DzC=PWsfYfEgk~8Y@zSl5@?JCAaf_`iC z7}wjPMT-_KTC|uP;00(E{qdPkok>iK7A;z|XwjlYQ}9=5Lw{V=cZB=&w?G~~c5nlR z{=9Am@J-L5UvZ5ZZopA9fRDPG0TXG!u`*!4t{E_vOF-ZL`b$pl(|*QeA2Wk*1iBH{vs9DlC z#@`!M@t+vz;&~jkX&>p|_WeVLfACPE&&Sg^ss#INYrn>;7`@-?&hNLvvAQir0_&E5 z(hV3ST>Dt}@iYz>h%z!DG6#%O-G#dWN*_<-@bveY*7j(bL~Gnd4$2B2PvhX1WD2aJ ze{Lu1#8(L)8w2>cTtyA@|ANQ>7wpH{=HqD`(fOUVEL002ovPDHLkV1f-pcVhqm literal 0 HcmV?d00001 diff --git a/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue b/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue index 7fc127e07..db46002f5 100644 --- a/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue +++ b/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue @@ -7,19 +7,19 @@ import { SkinPreviewRenderer, CapeButton, CapeLikeTextButton, - Button, RadioButtons + Button, RadioButtons, ButtonStyled } from '@modrinth/ui' import { add_and_equip_custom_skin, type Cape, determineModelType, - equip_skin, + equip_skin, remove_custom_skin, type Skin, type SkinModel } from '@/helpers/skins.ts' import {handleError} from '@/store/notifications' import {computed, ref, useTemplateRef, watch} from 'vue' -import {CheckCircleIcon, InfoIcon, UploadIcon, ChevronRightIcon} from "@modrinth/assets"; +import {CheckCircleIcon, InfoIcon, UploadIcon, ChevronRightIcon, CheckIcon, SaveIcon, XIcon, TrashIcon } from "@modrinth/assets"; const modal = useTemplateRef('modal') const mode = ref<'new' | 'edit'>('new') @@ -27,6 +27,7 @@ const currentSkin = ref(null) const textureBlob = ref(null) const variant = ref('Classic') const selectedCape = ref(undefined) +const tempSkinUrl = ref('/src/assets/skins/steve.png') const props = defineProps<{ capes?: Cape[] }>() const firstThreeCapes = computed(() => props.capes?.slice(0, 3) ?? []) @@ -49,27 +50,30 @@ watch(textureBlob, async (blob, prev) => { const previewSkin = computed(() => { if (localPreviewUrl.value) return localPreviewUrl.value - if (currentSkin.value) return currentSkin.value.texture; + if (currentSkin.value) return currentSkin.value.texture + if (mode.value === 'new') return tempSkinUrl.value return '' }) const emit = defineEmits<{ - (e: 'view-more-capes', ev: MouseEvent): void - (e: 'saved'): void + (e: 'saved', editedSkin: Skin | null, oldSkin: Skin): void + (e: 'deleted', deletedSkin: Skin): void }>() +const disableSave = computed(() => mode.value === 'new' && !textureBlob.value) + function resetState() { - mode.value = 'new' - currentSkin.value = null - textureBlob.value = null - fileName.value = null + mode.value = 'new' + currentSkin.value = null + textureBlob.value = null + fileName.value = null if (localPreviewUrl.value) { URL.revokeObjectURL(localPreviewUrl.value) localPreviewUrl.value = null } - variant.value = 'Classic' + variant.value = 'Classic' selectedCape.value = undefined } @@ -77,6 +81,13 @@ function show(e: MouseEvent, skin?: Skin) { mode.value = skin ? 'edit' : 'new' currentSkin.value = skin ?? null textureBlob.value = null + + if (!skin) { + tempSkinUrl.value = Math.random() <= 0.05 + ? '/src/assets/skins/herobrine.png' + : '/src/assets/skins/steve.png' + } + variant.value = skin?.variant ?? 'Classic' selectedCape.value = skin?.cape_id ? props.capes?.find(c => c.id === skin.cape_id) : undefined modal.value?.show(e) @@ -84,7 +95,7 @@ function show(e: MouseEvent, skin?: Skin) { function hide() { modal.value?.hide() - resetState() + setTimeout(resetState, 100); } async function onTextureSelected(files: FileList | null) { @@ -104,29 +115,40 @@ function selectCape(cape: Cape | undefined) { } function viewMoreCapes(e: MouseEvent) { - hide() - emit('view-more-capes', e) + // TODO: later +} + +async function handleDelete() { + try { + await remove_custom_skin(currentSkin.value!) + emit('deleted', currentSkin.value!) + hide(); + } catch (err) { + handleError(err); + } } async function save() { try { + let edited: Skin | null = null; if (mode.value === 'new') { if (!textureBlob.value) throw new Error('Please upload a skin texture first.') - await add_and_equip_custom_skin(textureBlob.value, variant.value, selectedCape.value).catch(handleError) + await add_and_equip_custom_skin(textureBlob.value, variant.value, selectedCape.value) } else if (currentSkin.value) { - const edited: Skin = { + edited = { ...currentSkin.value, variant: variant.value, - cape_id: selectedCape.value?.id + cape_id: selectedCape.value?.id, + is_equipped: true } if (textureBlob.value) { - await add_and_equip_custom_skin(textureBlob.value, variant.value, selectedCape.value).catch(handleError) + await add_and_equip_custom_skin(textureBlob.value, variant.value, selectedCape.value) } else { - await equip_skin(edited).catch(handleError) + await equip_skin(edited); } } + emit('saved', edited, currentSkin.value!) hide() - emit('saved') } catch (err) { handleError(err) } @@ -159,8 +181,8 @@ defineExpose({ wide-model-src="/src/assets/models/classic_player.gltf" :variant="variant" :texture-src="previewSkin" - scale="1.4" - fov="50" + :scale="1.4" + :fov="50" class="h-full w-full" /> @@ -180,11 +202,11 @@ defineExpose({ >