fix: lint issues

This commit is contained in:
Calum H. authored and Alejandro González committed 2025-06-14 17:45:05 +02:00
1 parent b3bc3a8731
commit 187e892c18
12 files changed
+319 -213

No files matched your search

@@ -44,7 +44,13 @@
</div>
<div v-else class="logged-out account">
<h4>Not signed in</h4>
<Button v-tooltip="'Log in'" :disabled="loginDisabled" icon-only color="primary" @click="login()">
<Button
v-tooltip="'Log in'"
:disabled="loginDisabled"
icon-only
color="primary"
@click="login()"
>
<LogInIcon v-if="!loginDisabled" />
<SpinnerIcon v-else class="animate-spin" />
</Button>
@@ -95,7 +101,7 @@ defineProps({
const emit = defineEmits(['change'])
const accounts = ref({})
const loginDisabled = ref(false);
const loginDisabled = ref(false)
const defaultUser = ref()
async function refreshValues() {
@@ -104,13 +110,13 @@ async function refreshValues() {
}
function setLoginDisabled(value) {
loginDisabled.value = value;
loginDisabled.value = value
}
defineExpose({
refreshValues,
setLoginDisabled,
loginDisabled
loginDisabled,
})
await refreshValues()
@@ -129,7 +135,7 @@ async function setAccount(account) {
}
async function login() {
loginDisabled.value = true;
loginDisabled.value = true
const loggedIn = await login_flow().catch(handleSevereError)
if (loggedIn) {
@@ -138,7 +144,7 @@ async function login() {
}
trackEvent('AccountLogIn')
loginDisabled.value = false;
loginDisabled.value = false
}
const logout = async (id) => {
@@ -16,7 +16,8 @@
:variant="variant"
:texture-src="previewSkin"
:cape-src="selectedCapeTexture"
:scale="1.4" :fov="50"
:scale="1.4"
:fov="50"
:initial-rotation="Math.PI / 8"
class="h-full w-full"
/>
@@ -26,14 +27,12 @@
<div class="flex flex-col gap-4 w-full min-h-[20rem]">
<section>
<h2 class="text-base font-semibold mb-2">Texture</h2>
<Button @click="openUploadSkinModal">
<UploadIcon /> Replace texture
</Button>
<Button @click="openUploadSkinModal"> <UploadIcon /> Replace texture </Button>
</section>
<section>
<h2 class="text-base font-semibold mb-2">Arm style</h2>
<RadioButtons v-model="variant" :items="['CLASSIC','SLIM']">
<RadioButtons v-model="variant" :items="['CLASSIC', 'SLIM']">
<template #default="{ item }">
{{ item === 'CLASSIC' ? 'Wide' : 'Slim' }}
</template>
@@ -69,7 +68,7 @@
tooltip="View more capes"
@mouseup="openSelectCapeModal"
>
<template #icon><ChevronRightIcon/></template>
<template #icon><ChevronRightIcon /></template>
<span>More</span>
</CapeLikeTextButton>
</div>
@@ -84,41 +83,49 @@
{{ mode === 'new' ? 'Add skin' : 'Save skin' }}
</Button>
</ButtonStyled>
<Button @click="hide"><XIcon/>Cancel</Button>
<Button @click="hide"><XIcon />Cancel</Button>
</div>
</NewModal>
<SelectCapeModal ref="selectCapeModal" :capes="capes || []" @select="handleCapeSelected" @cancel="handleCapeCancel" />
<SelectCapeModal
ref="selectCapeModal"
:capes="capes || []"
@select="handleCapeSelected"
@cancel="handleCapeCancel"
/>
</template>
<script setup lang="ts">
import { ref, computed, watch, useTemplateRef } from 'vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
import {
Card, FileInput, SkinPreviewRenderer,
Button, RadioButtons, CapeButton, CapeLikeTextButton, ButtonStyled,
NewModal
SkinPreviewRenderer,
Button,
RadioButtons,
CapeButton,
CapeLikeTextButton,
ButtonStyled,
NewModal,
} from '@modrinth/ui'
import {
add_and_equip_custom_skin,
remove_custom_skin,
unequip_skin,
type Skin, type Cape, type SkinModel
type Skin,
type Cape,
type SkinModel,
} from '@/helpers/skins.ts'
import { handleError } from '@/store/notifications'
import {
UploadIcon,
CheckIcon, SaveIcon, XIcon, TrashIcon, ChevronRightIcon
} from '@modrinth/assets'
import { UploadIcon, CheckIcon, SaveIcon, XIcon, ChevronRightIcon } from '@modrinth/assets'
const modal = useTemplateRef('modal')
const selectCapeModal = useTemplateRef('selectCapeModal')
const mode = ref<'new'|'edit'>('new')
const currentSkin = ref<Skin|null>(null)
const mode = ref<'new' | 'edit'>('new')
const currentSkin = ref<Skin | null>(null)
const shouldRestoreModal = ref(false)
const fileUploadTextureBlob = ref<Uint8Array|null>(null)
const fileName = ref<string|null>(null)
const fileUploadTextureBlob = ref<Uint8Array | null>(null)
const fileName = ref<string | null>(null)
watch(fileUploadTextureBlob, () => {
if (fileName.value === null && fileUploadTextureBlob.value) {
fileName.value = 'New upload'
@@ -126,7 +133,7 @@ watch(fileUploadTextureBlob, () => {
})
const variant = ref<SkinModel>('CLASSIC')
const selectedCape = ref<Cape|undefined>(undefined)
const selectedCape = ref<Cape | undefined>(undefined)
const props = defineProps<{ capes?: Cape[] }>()
const selectedCapeTexture = computed(() => selectedCape.value?.texture)
@@ -134,11 +141,11 @@ const visibleCapeList = ref<Cape[]>([])
const sortedCapes = computed(() => {
return [...(props.capes || [])].sort((a, b) => {
const nameA = (a.name || '').toLowerCase();
const nameB = (b.name || '').toLowerCase();
return nameA.localeCompare(nameB);
});
});
const nameA = (a.name || '').toLowerCase()
const nameB = (b.name || '').toLowerCase()
return nameA.localeCompare(nameB)
})
})
function initVisibleCapeList() {
if (!props.capes || props.capes.length === 0) {
@@ -163,10 +170,10 @@ function getSortedCapes(count: number): Cape[] {
function getSortedCapeExcluding(excludeId: string): Cape | undefined {
if (!sortedCapes.value || sortedCapes.value.length <= 1) return undefined
return sortedCapes.value.find(cape => cape.id !== excludeId)
return sortedCapes.value.find((cape) => cape.id !== excludeId)
}
const localPreviewUrl = ref<string|null>(null)
const localPreviewUrl = ref<string | null>(null)
watch(fileUploadTextureBlob, (blob, prev) => {
if (prev && localPreviewUrl.value) URL.revokeObjectURL(localPreviewUrl.value)
if (blob) localPreviewUrl.value = URL.createObjectURL(new Blob([blob]))
@@ -179,24 +186,25 @@ const previewSkin = computed(() => {
})
const hasEdits = computed(() => {
if (mode.value !== 'edit') return true;
if (fileUploadTextureBlob.value) return true;
if (!currentSkin.value) return false;
if (variant.value !== currentSkin.value.variant) return true;
if ((selectedCape.value?.id || null) !== (currentSkin.value.cape_id || null)) return true;
return false;
});
if (mode.value !== 'edit') return true
if (fileUploadTextureBlob.value) return true
if (!currentSkin.value) return false
if (variant.value !== currentSkin.value.variant) return true
if ((selectedCape.value?.id || null) !== (currentSkin.value.cape_id || null)) return true
return false
})
const disableSave = computed(() =>
(mode.value === 'new' && !fileUploadTextureBlob.value) ||
(mode.value === 'edit' && !hasEdits.value)
);
const disableSave = computed(
() =>
(mode.value === 'new' && !fileUploadTextureBlob.value) ||
(mode.value === 'edit' && !hasEdits.value),
)
const saveTooltip = computed(() => {
if (mode.value === 'new' && !fileUploadTextureBlob.value) return 'Upload a skin first!';
if (mode.value === 'edit' && !hasEdits.value) return 'Make an edit to the skin first!';
return undefined;
});
if (mode.value === 'new' && !fileUploadTextureBlob.value) return 'Upload a skin first!'
if (mode.value === 'edit' && !hasEdits.value) return 'Make an edit to the skin first!'
return undefined
})
function resetState() {
mode.value = 'new'
@@ -218,7 +226,7 @@ function show(e: MouseEvent, skin?: Skin) {
currentSkin.value = skin ?? null
if (skin) {
variant.value = skin.variant
selectedCape.value = props.capes?.find(c=>c.id===skin.cape_id)
selectedCape.value = props.capes?.find((c) => c.id === skin.cape_id)
} else {
variant.value = 'CLASSIC'
selectedCape.value = undefined
@@ -243,7 +251,7 @@ function showNew(e: MouseEvent, skinTexture: Uint8Array, filename: string) {
function restoreWithNewTexture(skinTexture: Uint8Array, filename: string) {
fileUploadTextureBlob.value = skinTexture
fileName.value = filename
if (shouldRestoreModal.value) {
setTimeout(() => {
modal.value?.show()
@@ -257,12 +265,12 @@ function hide() {
setTimeout(() => resetState(), 250)
}
function selectCape(cape: Cape|undefined) {
function selectCape(cape: Cape | undefined) {
if (cape && selectedCape.value?.id !== cape.id) {
const isInVisibleList = visibleCapeList.value.some(c => c.id === cape.id)
const isInVisibleList = visibleCapeList.value.some((c) => c.id === cape.id)
if (!isInVisibleList && visibleCapeList.value.length > 0) {
visibleCapeList.value.splice(0, 1, cape)
if (visibleCapeList.value.length > 1 && visibleCapeList.value[1].id === cape.id) {
const otherCape = getSortedCapeExcluding(cape.id)
if (otherCape) {
@@ -306,7 +314,7 @@ function openSelectCapeModal(e: MouseEvent) {
currentSkin.value?.texture_key,
selectedCape.value,
previewSkin.value,
variant.value
variant.value,
)
}, 0)
}
@@ -329,7 +337,7 @@ async function save() {
blob = new Uint8Array(buf)
}
await unequip_skin();
await unequip_skin()
if (mode.value === 'new') {
await add_and_equip_custom_skin(blob, variant.value, selectedCape.value)
@@ -346,9 +354,13 @@ async function save() {
}
}
watch(() => props.capes, () => {
initVisibleCapeList()
}, { immediate: true })
watch(
() => props.capes,
() => {
initVisibleCapeList()
},
{ immediate: true },
)
const emit = defineEmits<{
(event: 'saved'): void
@@ -361,6 +373,6 @@ defineExpose({
showNew,
restoreWithNewTexture,
hide,
shouldRestoreModal
shouldRestoreModal,
})
</script>
@@ -7,7 +7,7 @@ import {
CapeButton,
CapeLikeTextButton,
SkinPreviewRenderer,
NewModal
NewModal,
} from '@modrinth/ui'
import { CheckIcon, XIcon } from '@modrinth/assets'
@@ -24,11 +24,11 @@ const props = defineProps<{
const sortedCapes = computed(() => {
return [...props.capes].sort((a, b) => {
const nameA = (a.name || '').toLowerCase();
const nameB = (b.name || '').toLowerCase();
return nameA.localeCompare(nameB);
});
});
const nameA = (a.name || '').toLowerCase()
const nameB = (b.name || '').toLowerCase()
return nameA.localeCompare(nameB)
})
})
const currentSkinId = ref<string | undefined>()
const currentSkinTexture = ref<string | undefined>()
@@ -93,7 +93,7 @@ defineExpose({
:variant="currentSkinVariant"
:scale="1.4"
:fov="50"
:initial-rotation="Math.PI + (Math.PI / 8)"
:initial-rotation="Math.PI + Math.PI / 8"
class="h-full w-full"
/>
</div>
@@ -1,9 +1,7 @@
<template>
<NewModal ref="modal" @on-hide="hide(true)">
<template #title>
<span class="text-lg font-extrabold text-contrast">
Upload skin texture
</span>
<span class="text-lg font-extrabold text-contrast"> Upload skin texture </span>
</template>
<div class="relative">
<div
@@ -12,10 +10,22 @@
@drop.prevent="handleFileOperation"
@dragover.prevent
>
<p class="mx-auto mb-0 text-primary text-xl text-center flex items-center gap-2"><UploadIcon /> Select skin texture file</p>
<p class="mx-auto mt-0 text-secondary text-sm text-center">Drag and drop or click here to browse</p>
<p class="mx-auto mt-0 text-secondary text-xs text-center">Only 64x64 PNG files are accepted</p>
<input ref="fileInput" type="file" accept="image/png" class="hidden" @change="handleFileOperation" />
<p class="mx-auto mb-0 text-primary text-xl text-center flex items-center gap-2">
<UploadIcon /> Select skin texture file
</p>
<p class="mx-auto mt-0 text-secondary text-sm text-center">
Drag and drop or click here to browse
</p>
<p class="mx-auto mt-0 text-secondary text-xs text-center">
Only 64x64 PNG files are accepted
</p>
<input
ref="fileInput"
type="file"
accept="image/png"
class="hidden"
@change="handleFileOperation"
/>
</div>
</div>
</NewModal>
@@ -24,7 +34,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import { UploadIcon } from '@modrinth/assets'
import { useNotifications } from "@/store/state"
import { useNotifications } from '@/store/state'
import { NewModal } from '@modrinth/ui'
const notifications = useNotifications()
@@ -71,30 +81,30 @@ async function validateImageDimensions(file: File): Promise<boolean> {
async function handleFileOperation(e: Event | DragEvent) {
// Get files from either drag event or file input
const files = (e as DragEvent).dataTransfer?.files || (e.target as HTMLInputElement).files;
const files = (e as DragEvent).dataTransfer?.files || (e.target as HTMLInputElement).files
if (!files || files.length === 0) {
return;
return
}
const file = files[0];
const file = files[0]
if (file.type !== 'image/png') {
notifications.addNotification({
title: 'Invalid file type.',
text: 'Only PNG files are accepted.',
type: 'error',
});
return;
})
return
}
const isValidDimensions = await validateImageDimensions(file);
const isValidDimensions = await validateImageDimensions(file)
if (!isValidDimensions) {
notifications.addNotification({
title: 'Invalid dimensions.',
text: 'Only 64x64 PNG files are accepted.',
type: 'error',
});
return;
})
return
}
emit('uploaded', file);
hide();
emit('uploaded', file)
hide()
}
defineExpose({ show, hide })
+90 -61
View File
@@ -1,14 +1,22 @@
<script setup lang="ts">
import { UpdatedIcon, PlusIcon, ExcitedRinthbot, LogInIcon, SpinnerIcon, EditIcon, TrashIcon } from '@modrinth/assets'
import {
UpdatedIcon,
PlusIcon,
ExcitedRinthbot,
LogInIcon,
SpinnerIcon,
EditIcon,
TrashIcon,
} from '@modrinth/assets'
import {
ButtonStyled,
SkinPreviewRenderer,
SkinButton,
SkinLikeTextButton,
Button,
ConfirmModal
ConfirmModal,
} from '@modrinth/ui'
import type { Ref } from 'vue';
import type { Ref } from 'vue'
import { ref, computed, useTemplateRef, watch, onMounted, onUnmounted, inject } from 'vue'
import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
@@ -28,9 +36,9 @@ import type { Cape, Skin } from '@/helpers/skins.ts'
import { get_default_user, users, login as login_flow } from '@/helpers/auth'
import type { RenderResult } from '@/helpers/rendering/batchSkinRenderer.ts'
import { generateSkinPreviews, map } from '@/helpers/rendering/batchSkinRenderer.ts'
import { handleSevereError } from "@/store/error";
import { trackEvent } from "@/helpers/analytics";
import type AccountsCard from "@/components/ui/AccountsCard.vue";
import { handleSevereError } from '@/store/error'
import { trackEvent } from '@/helpers/analytics'
import type AccountsCard from '@/components/ui/AccountsCard.vue'
const editSkinModal = useTemplateRef('editSkinModal')
const selectCapeModal = useTemplateRef('selectCapeModal')
@@ -64,7 +72,9 @@ const currentCape = computed(() => {
const skinTexture = computed(() => selectedSkin.value?.texture ?? '')
const capeTexture = computed(() => currentCape.value?.texture)
const skinVariant = computed(() => selectedSkin.value?.variant)
const skinNametag = computed(() => settings.value.hide_nametag_skins_page ? undefined : username.value)
const skinNametag = computed(() =>
settings.value.hide_nametag_skins_page ? undefined : username.value,
)
let userCheckInterval: number | null = null
@@ -141,7 +151,7 @@ function getBakedSkinTextures(skin: Skin): RenderResult | undefined {
}
async function login() {
accountsCard.value.setLoginDisabled(true);
accountsCard.value.setLoginDisabled(true)
const loggedIn = await login_flow().catch(handleSevereError)
if (loggedIn && accountsCard) {
@@ -149,7 +159,7 @@ async function login() {
}
trackEvent('AccountLogIn')
accountsCard.value.setLoginDisabled(false);
accountsCard.value.setLoginDisabled(false)
}
function openUploadSkinModal(e: MouseEvent) {
@@ -158,9 +168,9 @@ function openUploadSkinModal(e: MouseEvent) {
function onSkinFileUploaded(file: File) {
const fakeEvent = new MouseEvent('click')
file.arrayBuffer().then(buf => {
file.arrayBuffer().then((buf) => {
const skinTexture = new Uint8Array(buf)
if (editSkinModal.value && editSkinModal.value.shouldRestoreModal) {
editSkinModal.value.restoreWithNewTexture(skinTexture, file.name)
} else {
@@ -181,16 +191,16 @@ function onUploadCanceled() {
watch(
() => selectedSkin.value?.cape_id,
() => {}
() => {},
)
onMounted(() => {
userCheckInterval = window.setInterval(checkUserChanges, 250);
userCheckInterval = window.setInterval(checkUserChanges, 250)
})
onUnmounted(() => {
if (userCheckInterval !== null) {
window.clearInterval(userCheckInterval);
window.clearInterval(userCheckInterval)
}
})
@@ -198,9 +208,9 @@ async function checkUserChanges() {
try {
const defaultId = await get_default_user()
if (defaultId !== currentUserId.value) {
await loadCurrentUser();
await loadCapes();
await loadSkins();
await loadCurrentUser()
await loadCapes()
await loadSkins()
}
} catch (error) {
if (currentUser.value) {
@@ -221,7 +231,11 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
@open-upload-modal="openUploadSkinModal"
/>
<SelectCapeModal ref="selectCapeModal" :capes="capes" @select="handleCapeSelected" />
<UploadSkinModal ref="uploadSkinModal" @uploaded="onSkinFileUploaded" @canceled="onUploadCanceled" />
<UploadSkinModal
ref="uploadSkinModal"
@uploaded="onSkinFileUploaded"
@canceled="onUploadCanceled"
/>
<ConfirmModal
ref="deleteSkinModal"
title="Are you sure you want to delete this skin?"
@@ -237,20 +251,21 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
<ButtonStyled :disabled="!!selectedSkin?.cape_id">
<button
v-tooltip="
selectedSkin?.cape_id
? 'The equipped skin is overriding the default cape.'
: undefined
"
selectedSkin?.cape_id
? 'The equipped skin is overriding the default cape.'
: undefined
"
:disabled="!!selectedSkin?.cape_id"
@click="
(e: MouseEvent) => selectCapeModal?.show(
e,
selectedSkin?.texture_key,
currentCape,
skinTexture,
skinVariant
)
"
(e: MouseEvent) =>
selectCapeModal?.show(
e,
selectedSkin?.texture_key,
currentCape,
skinTexture,
skinVariant,
)
"
>
<UpdatedIcon />
Change cape
@@ -275,15 +290,11 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
<section class="flex flex-col gap-2 mt-1">
<h2 class="text-lg font-bold m-0 text-primary">Saved skins</h2>
<div class="skin-card-grid">
<SkinLikeTextButton
class="skin-card"
tooltip="Add a skin"
@click="openUploadSkinModal"
>
<template #icon>
<PlusIcon class="w-5 h-5 stroke-2" />
</template>
<span>Add a skin</span>
<SkinLikeTextButton class="skin-card" tooltip="Add a skin" @click="openUploadSkinModal">
<template #icon>
<PlusIcon class="w-5 h-5 stroke-2" />
</template>
<span>Add a skin</span>
</SkinLikeTextButton>
<SkinButton
@@ -296,24 +307,24 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
@select="changeSkin(skin)"
>
<template #overlay-buttons>
<Button
color="green"
aria-label="Edit skin"
@click.stop="(e) => editSkinModal?.show(e, skin)"
>
<EditIcon class="w-5 h-5" /> Edit
</Button>
<Button
v-show="!skin.is_equipped"
v-tooltip="'Delete skin'"
aria-label="Delete skin"
color="red"
class="!rounded-[100%]"
icon-only
@click.stop="() => confirmDeleteSkin(skin)"
>
<TrashIcon class="w-5 h-5" />
</Button>
<Button
color="green"
aria-label="Edit skin"
@click.stop="(e) => editSkinModal?.show(e, skin)"
>
<EditIcon class="w-5 h-5" /> Edit
</Button>
<Button
v-show="!skin.is_equipped"
v-tooltip="'Delete skin'"
aria-label="Delete skin"
color="red"
class="!rounded-[100%]"
icon-only
@click.stop="() => confirmDeleteSkin(skin)"
>
<TrashIcon class="w-5 h-5" />
</Button>
</template>
</SkinButton>
</div>
@@ -338,14 +349,32 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
</div>
<div v-else class="flex items-center justify-center min-h-[50vh] pt-[25%]">
<div class="bg-bg-raised rounded-lg p-7 flex flex-col gap-5 shadow-md relative max-w-xl w-full mx-auto">
<img :src="ExcitedRinthbot" alt="Excited Modrinth Bot" class="absolute -top-28 right-8 md:right-20 h-28 w-auto" />
<div class="absolute top-0 left-0 w-full h-[1px] opacity-40 bg-gradient-to-r from-transparent via-green-500 to-transparent" style="background: linear-gradient(to right, transparent 2rem, var(--color-green) calc(100% - 13rem), var(--color-green) calc(100% - 5rem), transparent calc(100% - 2rem))"></div>
<div
class="bg-bg-raised rounded-lg p-7 flex flex-col gap-5 shadow-md relative max-w-xl w-full mx-auto"
>
<img
:src="ExcitedRinthbot"
alt="Excited Modrinth Bot"
class="absolute -top-28 right-8 md:right-20 h-28 w-auto"
/>
<div
class="absolute top-0 left-0 w-full h-[1px] opacity-40 bg-gradient-to-r from-transparent via-green-500 to-transparent"
style="
background: linear-gradient(
to right,
transparent 2rem,
var(--color-green) calc(100% - 13rem),
var(--color-green) calc(100% - 5rem),
transparent calc(100% - 2rem)
);
"
></div>
<div class="flex flex-col gap-5">
<h1 class="text-3xl font-extrabold m-0">Please sign-in</h1>
<p class="text-lg m-0">
Please sign into your Minecraft account to use the skin management features of the Modrinth app.
Please sign into your Minecraft account to use the skin management features of the
Modrinth app.
</p>
<ButtonStyled v-show="accountsCard" color="brand" :disabled="accountsCard.loginDisabled">
<Button :disabled="accountsCard.loginDisabled" @click="login">