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 000000000..2d964e9ac Binary files /dev/null and b/apps/app-frontend/src/assets/skins/herobrine.png differ 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 000000000..f97afdd5a Binary files /dev/null and b/apps/app-frontend/src/assets/skins/steve.png differ 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({ >