refactor(list): improve form

zio/stable
Kevin 2023-03-20 23:41:50 +08:00
parent 01d1a30413
commit 0418d05753
No known key found for this signature in database
GPG Key ID: 68D73816CD641CDB
2 changed files with 55 additions and 75 deletions

View File

@ -1,117 +1,102 @@
<script setup lang="ts"> <script setup lang="ts">
import type { mastodon } from 'masto' import type { mastodon } from 'masto'
import { useForm } from 'slimeform'
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'listUpdated', list: mastodon.v1.List): void (e: 'listUpdated', list: mastodon.v1.List): void
(e: 'listRemoved', id: string): void (e: 'listRemoved', id: string): void
}>() }>()
const { list } = $defineProps<{ const { list } = defineModel<{
list: mastodon.v1.List list: mastodon.v1.List
}>() }>()
const { modelValue } = defineModel<{
modelValue: string
}>()
modelValue.value = list.title
const { t } = useI18n() const { t } = useI18n()
const client = useMastoClient() const client = useMastoClient()
const { form, isDirty, submitter, reset } = useForm({
form: () => ({ ...list.value }),
})
let isEditing = $ref<boolean>(false) let isEditing = $ref<boolean>(false)
let busy = $ref<boolean>(false) let deleting = $ref<boolean>(false)
let deleteBusy = $ref<boolean>(false)
let actionError = $ref<string | undefined>(undefined) let actionError = $ref<string | undefined>(undefined)
const enableSaveButton = computed(() => list.title !== modelValue.value) const input = ref<HTMLInputElement>()
const editBtn = ref<HTMLButtonElement>()
const deleteBtn = ref<HTMLButtonElement>()
const edit = ref() const prepareEdit = async () => {
const deleteBtn = ref()
const input = ref()
const prepareEdit = () => {
isEditing = true isEditing = true
actionError = undefined actionError = undefined
nextTick(() => { await nextTick()
input.value?.focus() input.value?.focus()
})
} }
const cancelEdit = () => { const cancelEdit = async () => {
isEditing = false isEditing = false
actionError = undefined actionError = undefined
modelValue.value = list.title reset()
nextTick(() => {
edit.value?.focus()
})
}
async function finishEditing() {
if (busy || !isEditing || !enableSaveButton.value)
return
busy = true
actionError = undefined
await nextTick() await nextTick()
editBtn.value?.focus()
}
const { submit, submitting } = submitter(async () => {
try { try {
const updateList = await client.v1.lists.update(list.id, { list.value = await client.v1.lists.update(form.id, {
title: modelValue.value, title: form.title,
}) })
cancelEdit() cancelEdit()
emit('listUpdated', updateList)
} }
catch (err) { catch (err) {
console.error(err) console.error(err)
actionError = (err as Error).message actionError = (err as Error).message
nextTick(() => { await nextTick()
input.value?.focus() input.value?.focus()
}
}) })
}
finally {
busy = false
}
}
async function removeList() { async function removeList() {
if (deleteBusy) if (deleting)
return return
deleteBusy = true
actionError = undefined
await nextTick()
const confirmDelete = await openConfirmDialog({ const confirmDelete = await openConfirmDialog({
title: t('confirm.delete_list.title', [list.title]), title: t('confirm.delete_list.title', [list.value.title]),
confirm: t('confirm.delete_list.confirm'), confirm: t('confirm.delete_list.confirm'),
cancel: t('confirm.delete_list.cancel'), cancel: t('confirm.delete_list.cancel'),
}) })
deleting = true
actionError = undefined
await nextTick()
if (confirmDelete === 'confirm') { if (confirmDelete === 'confirm') {
await nextTick() await nextTick()
try { try {
await client.v1.lists.remove(list.id) await client.v1.lists.remove(list.value.id)
emit('listRemoved', list.id) emit('listRemoved', list.value.id)
} }
catch (err) { catch (err) {
console.error(err) console.error(err)
actionError = (err as Error).message actionError = (err as Error).message
nextTick(() => { await nextTick()
deleteBtn.value?.focus() deleteBtn.value?.focus()
})
} }
finally { finally {
deleteBusy = false deleting = false
} }
} }
else { else {
deleteBusy = false deleting = false
} }
} }
function clearError() { async function clearError() {
actionError = undefined actionError = undefined
nextTick(() => { await nextTick()
if (isEditing) if (isEditing)
input.value?.focus() input.value?.focus()
else else
deleteBtn.value?.focus() deleteBtn.value?.focus()
})
} }
onDeactivated(cancelEdit) onDeactivated(cancelEdit)
@ -122,7 +107,7 @@ onDeactivated(cancelEdit)
hover:bg-active flex justify-between items-center gap-x-2 hover:bg-active flex justify-between items-center gap-x-2
:aria-describedby="actionError ? `action-list-error-${list.id}` : undefined" :aria-describedby="actionError ? `action-list-error-${list.id}` : undefined"
:class="actionError ? 'border border-base border-rounded rounded-be-is-0 rounded-be-ie-0 border-b-unset border-$c-danger-active' : null" :class="actionError ? 'border border-base border-rounded rounded-be-is-0 rounded-be-ie-0 border-b-unset border-$c-danger-active' : null"
@submit.prevent="finishEditing" @submit.prevent="submit"
> >
<div <div
v-if="isEditing" v-if="isEditing"
@ -141,20 +126,15 @@ onDeactivated(cancelEdit)
</CommonTooltip> </CommonTooltip>
<input <input
ref="input" ref="input"
v-model="modelValue" v-model="form.title"
rounded-3 rounded-3 w-full bg-transparent
w-full outline="focus:none" pe-4 pb="1px"
bg-transparent flex-1 placeholder-text-secondary
outline="focus:none"
pe-4
pb="1px"
flex-1
placeholder-text-secondary
@keydown.esc="cancelEdit()" @keydown.esc="cancelEdit()"
> >
</div> </div>
<NuxtLink v-else :to="`list/${list.id}`" block grow p4> <NuxtLink v-else :to="`list/${list.id}`" block grow p4>
{{ list.title }} {{ form.title }}
</NuxtLink> </NuxtLink>
<div mr4 flex gap2> <div mr4 flex gap2>
<CommonTooltip v-if="isEditing" :content="$t('list.save')" no-auto-focus> <CommonTooltip v-if="isEditing" :content="$t('list.save')" no-auto-focus>
@ -163,10 +143,10 @@ onDeactivated(cancelEdit)
text-sm p2 border-1 transition-colors text-sm p2 border-1 transition-colors
border-dark hover:text-primary border-dark hover:text-primary
btn-action-icon btn-action-icon
:disabled="deleteBusy || !enableSaveButton || busy" :disabled="deleting || !isDirty || submitting"
> >
<template v-if="isEditing"> <template v-if="isEditing">
<span v-if="busy" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip"> <span v-if="submitting" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip">
<span block i-ri:loader-2-fill aria-hidden="true" /> <span block i-ri:loader-2-fill aria-hidden="true" />
</span> </span>
<span v-else block text-current i-ri:save-2-fill class="rtl-flip" /> <span v-else block text-current i-ri:save-2-fill class="rtl-flip" />
@ -175,7 +155,7 @@ onDeactivated(cancelEdit)
</CommonTooltip> </CommonTooltip>
<CommonTooltip v-else :content="$t('list.edit')" no-auto-focus> <CommonTooltip v-else :content="$t('list.edit')" no-auto-focus>
<button <button
ref="edit" ref="editBtn"
type="button" type="button"
text-sm p2 border-1 transition-colors text-sm p2 border-1 transition-colors
border-dark hover:text-primary border-dark hover:text-primary
@ -194,7 +174,7 @@ onDeactivated(cancelEdit)
:disabled="isEditing" :disabled="isEditing"
@click.prevent="removeList" @click.prevent="removeList"
> >
<span v-if="deleteBusy" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip"> <span v-if="deleting" aria-hidden="true" block animate animate-spin preserve-3d class="rtl-flip">
<span block i-ri:loader-2-fill aria-hidden="true" /> <span block i-ri:loader-2-fill aria-hidden="true" />
</span> </span>
<span v-else block text-current i-ri:delete-bin-2-line class="rtl-flip" /> <span v-else block text-current i-ri:delete-bin-2-line class="rtl-flip" />

View File

@ -16,7 +16,7 @@ useHeadFixed({
}) })
const paginatorRef = ref() const paginatorRef = ref()
const inputRef = ref() const inputRef = ref<HTMLInputElement>()
let actionError = $ref<string | undefined>(undefined) let actionError = $ref<string | undefined>(undefined)
let busy = $ref<boolean>(false) let busy = $ref<boolean>(false)
const createText = ref('') const createText = ref('')
@ -78,7 +78,7 @@ onDeactivated(() => clearError(false))
<template #default="{ item }"> <template #default="{ item }">
<ListEntry <ListEntry
:list="item" :list="item"
@list-updated="updateEntry" @update:list="updateEntry"
@list-removed="removeEntry" @list-removed="removeEntry"
/> />
</template> </template>