refactor(list): improve form
parent
01d1a30413
commit
0418d05753
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue