fix: polish
This commit is contained in:
@@ -58,9 +58,9 @@ watch(
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Hide Nametag</h2>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Hide nametag</h2>
|
||||
<p class="m-0 mt-1">
|
||||
Allows you to disable the nametag that appears above the main preview on the skin management
|
||||
Disables the nametag above your player on the skins page.
|
||||
page.
|
||||
</p>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ watch(
|
||||
|
||||
<div v-if="os !== 'MacOS'" class="mt-4 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Native Decorations</h2>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Native decorations</h2>
|
||||
<p class="m-0 mt-1">Use system window frame (app restart required).</p>
|
||||
</div>
|
||||
<Toggle id="native-decorations" v-model="settings.native_decorations" />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<NewModal ref="modal" @on-hide="resetState">
|
||||
<UploadSkinModal ref="uploadModal" />
|
||||
<ModalWrapper ref="modal" @on-hide="resetState">
|
||||
<template #title>
|
||||
<span class="text-lg font-extrabold text-contrast">
|
||||
{{ mode === 'edit' ? 'Edit skin' : 'New skin' }}
|
||||
{{ mode === 'edit' ? 'Editing skin' : 'Adding a skin' }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -78,16 +79,16 @@
|
||||
|
||||
<div class="flex gap-2 mt-12">
|
||||
<ButtonStyled color="brand" :disabled="disableSave || isSaving">
|
||||
<Button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save">
|
||||
<button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save">
|
||||
<SpinnerIcon v-if="isSaving" class="animate-spin" />
|
||||
<CheckIcon v-else-if="mode === 'new'" />
|
||||
<SaveIcon v-else />
|
||||
{{ mode === 'new' ? 'Add skin' : 'Save skin' }}
|
||||
</Button>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<Button :disabled="isSaving" @click="hide"><XIcon />Cancel</Button>
|
||||
</div>
|
||||
</NewModal>
|
||||
</ModalWrapper>
|
||||
|
||||
<SelectCapeModal
|
||||
ref="selectCapeModal"
|
||||
@@ -127,6 +128,8 @@ import {
|
||||
ChevronRightIcon,
|
||||
SpinnerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import ModalWrapper from "@/components/ui/modal/ModalWrapper.vue";
|
||||
import UploadSkinModal from "@/components/ui/skin/UploadSkinModal.vue";
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
const selectCapeModal = useTemplateRef('selectCapeModal')
|
||||
@@ -337,6 +340,16 @@ function openUploadSkinModal(e: MouseEvent) {
|
||||
emit('open-upload-modal', e)
|
||||
}
|
||||
|
||||
function restoreModal() {
|
||||
if (shouldRestoreModal.value) {
|
||||
setTimeout(() => {
|
||||
const fakeEvent = new MouseEvent('click')
|
||||
modal.value?.show(fakeEvent)
|
||||
shouldRestoreModal.value = false
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
isSaving.value = true
|
||||
|
||||
@@ -394,5 +407,6 @@ defineExpose({
|
||||
restoreWithNewTexture,
|
||||
hide,
|
||||
shouldRestoreModal,
|
||||
restoreModal
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
CapeButton,
|
||||
CapeLikeTextButton,
|
||||
SkinPreviewRenderer,
|
||||
NewModal,
|
||||
} from '@modrinth/ui'
|
||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import ModalWrapper from "@/components/ui/modal/ModalWrapper.vue";
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
|
||||
@@ -74,7 +74,7 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<NewModal ref="modal" @on-hide="onModalHide">
|
||||
<ModalWrapper ref="modal" @on-hide="onModalHide">
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-extrabold text-heading">Change cape</span>
|
||||
@@ -139,5 +139,5 @@ defineExpose({
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</NewModal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NewModal ref="modal" @on-hide="hide(true)">
|
||||
<ModalWrapper ref="modal" @on-hide="hide(true)">
|
||||
<template #title>
|
||||
<span class="text-lg font-extrabold text-contrast"> Upload skin texture </span>
|
||||
</template>
|
||||
@@ -7,10 +7,8 @@
|
||||
<div
|
||||
class="border-2 border-dashed border-highlight-gray rounded-xl h-[173px] flex flex-col items-center justify-center p-8 cursor-pointer bg-button-bg hover:bg-button-hover transition-colors relative"
|
||||
@click="triggerFileInput"
|
||||
@drop.prevent="handleFileOperation"
|
||||
@dragover.prevent
|
||||
>
|
||||
<p class="mx-auto mb-0 text-primary text-xl text-center flex items-center gap-2">
|
||||
<p class="mx-auto mb-0 text-primary font-bold text-lg 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">
|
||||
@@ -21,23 +19,27 @@
|
||||
type="file"
|
||||
accept="image/png"
|
||||
class="hidden"
|
||||
@change="handleFileOperation"
|
||||
@change="handleInputFileChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onBeforeUnmount, watch } from 'vue'
|
||||
import { UploadIcon } from '@modrinth/assets'
|
||||
import { useNotifications } from '@/store/state'
|
||||
import { NewModal } from '@modrinth/ui'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { readFile } from '@tauri-apps/plugin-fs'
|
||||
import ModalWrapper from "@/components/ui/modal/ModalWrapper.vue";
|
||||
|
||||
const notifications = useNotifications()
|
||||
|
||||
const modal = ref()
|
||||
const fileInput = ref<HTMLInputElement>()
|
||||
const unlisten = ref<() => void>()
|
||||
const modalVisible = ref(false)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'uploaded', file: File): void
|
||||
@@ -46,10 +48,14 @@ const emit = defineEmits<{
|
||||
|
||||
function show(e?: MouseEvent) {
|
||||
modal.value?.show(e)
|
||||
modalVisible.value = true
|
||||
setupDragDropListener()
|
||||
}
|
||||
|
||||
function hide(emitCanceled = false) {
|
||||
modal.value?.hide()
|
||||
modalVisible.value = false
|
||||
cleanupDragDropListener()
|
||||
resetState()
|
||||
if (emitCanceled) {
|
||||
emit('canceled')
|
||||
@@ -64,28 +70,77 @@ function triggerFileInput() {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
async function validateImageDimensions(file: File): Promise<boolean> {
|
||||
async function validateImageDimensions(blob: Blob): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(img.src)
|
||||
resolve(img.width === 64 && (img.height === 64 || img.height == 32))
|
||||
resolve(img.width === 64 && (img.height === 64 || img.height === 32))
|
||||
}
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(img.src)
|
||||
resolve(false)
|
||||
}
|
||||
img.src = URL.createObjectURL(file)
|
||||
img.src = URL.createObjectURL(blob)
|
||||
})
|
||||
}
|
||||
|
||||
async function handleFileOperation(e: Event | DragEvent) {
|
||||
const files = (e as DragEvent).dataTransfer?.files || (e.target as HTMLInputElement).files
|
||||
async function handleInputFileChange(e: Event) {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (!files || files.length === 0) {
|
||||
return
|
||||
}
|
||||
const file = files[0]
|
||||
if (file.type !== 'image/png') {
|
||||
await processFile(file)
|
||||
}
|
||||
|
||||
async function setupDragDropListener() {
|
||||
try {
|
||||
if (modalVisible.value) {
|
||||
await cleanupDragDropListener()
|
||||
unlisten.value = await getCurrentWebview().onDragDropEvent(async (event) => {
|
||||
if (event.payload.type !== 'drop') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!event.payload.paths || event.payload.paths.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = event.payload.paths[0]
|
||||
|
||||
try {
|
||||
const data = await readFile(filePath);
|
||||
|
||||
const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || 'skin.png'
|
||||
const fileBlob = new Blob([data], { type: 'image/png' })
|
||||
const file = new File([fileBlob], fileName, { type: 'image/png' })
|
||||
|
||||
await processFile(file)
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notifications.addNotification({
|
||||
title: 'Error processing file',
|
||||
text: 'Failed to read the dropped file.',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to set up drag and drop listener:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupDragDropListener() {
|
||||
if (unlisten.value) {
|
||||
unlisten.value()
|
||||
unlisten.value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function processFile(file: File) {
|
||||
if (!file.name.toLowerCase().endsWith('.png') && file.type !== 'image/png') {
|
||||
notifications.addNotification({
|
||||
title: 'Invalid file type.',
|
||||
text: 'Only PNG files are accepted.',
|
||||
@@ -93,6 +148,7 @@ async function handleFileOperation(e: Event | DragEvent) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const isValidDimensions = await validateImageDimensions(file)
|
||||
if (!isValidDimensions) {
|
||||
notifications.addNotification({
|
||||
@@ -102,9 +158,22 @@ async function handleFileOperation(e: Event | DragEvent) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
emit('uploaded', file)
|
||||
hide()
|
||||
}
|
||||
|
||||
watch(modalVisible, (isVisible) => {
|
||||
if (isVisible) {
|
||||
setupDragDropListener()
|
||||
} else {
|
||||
cleanupDragDropListener()
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanupDragDropListener()
|
||||
})
|
||||
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
|
||||
@@ -33,12 +33,12 @@ export class SkinPreviewStorage {
|
||||
async store(key: string, result: RenderResult): Promise<void> {
|
||||
if (!this.db) await this.init()
|
||||
|
||||
const transaction = this.db!.transaction(['previews'], 'readwrite')
|
||||
const store = transaction.objectStore('previews')
|
||||
|
||||
const forwardsBlob = await fetch(result.forwards).then((r) => r.blob())
|
||||
const backwardsBlob = await fetch(result.backwards).then((r) => r.blob())
|
||||
|
||||
const transaction = this.db!.transaction(['previews'], 'readwrite')
|
||||
const store = transaction.objectStore('previews')
|
||||
|
||||
const storedPreview: StoredPreview = {
|
||||
forwards: forwardsBlob,
|
||||
backwards: backwardsBlob,
|
||||
|
||||
@@ -252,13 +252,8 @@ function onSkinFileUploaded(file: File) {
|
||||
}
|
||||
|
||||
function onUploadCanceled() {
|
||||
if (editSkinModal.value && editSkinModal.value.shouldRestoreModal) {
|
||||
setTimeout(() => {
|
||||
const fakeEvent = new MouseEvent('click')
|
||||
editSkinModal.value!.show(fakeEvent)
|
||||
editSkinModal.value!.shouldRestoreModal = false
|
||||
}, 0)
|
||||
}
|
||||
console.log("fuck fuck fuck")
|
||||
editSkinModal.value?.restoreModal()
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -363,7 +358,7 @@ 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">
|
||||
<SkinLikeTextButton class="skin-card" @click="openUploadSkinModal">
|
||||
<template #icon>
|
||||
<PlusIcon class="size-8" />
|
||||
</template>
|
||||
@@ -382,10 +377,9 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
|
||||
<template #overlay-buttons>
|
||||
<Button
|
||||
color="green"
|
||||
class="pointer-events-auto"
|
||||
aria-label="Edit skin"
|
||||
@click.stop="(e) => editSkinModal?.show(e, skin)"
|
||||
>
|
||||
class="pointer-events-auto">
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Button
|
||||
@@ -451,11 +445,11 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
|
||||
Modrinth app.
|
||||
</p>
|
||||
<ButtonStyled v-show="accountsCard" color="brand" :disabled="accountsCard.loginDisabled">
|
||||
<Button :disabled="accountsCard.loginDisabled" @click="login">
|
||||
<button :disabled="accountsCard.loginDisabled" @click="login">
|
||||
<LogInIcon v-if="!accountsCard.loginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
Sign In
|
||||
</Button>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user