mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 17:10:07 +00:00
UX: Add progress and confirmation to deletion
This commit is contained in:
@@ -2,12 +2,14 @@
|
||||
<section class="w-full flex gap-2 justify-end px-2 pb-2">
|
||||
<TextButton
|
||||
:label="cancelTextX"
|
||||
:disabled
|
||||
type="transparent"
|
||||
autofocus
|
||||
@click="$emit('cancel')"
|
||||
/>
|
||||
<TextButton
|
||||
:label="confirmTextX"
|
||||
:disabled
|
||||
type="transparent"
|
||||
:class="confirmClass"
|
||||
@click="$emit('confirm')"
|
||||
@@ -15,17 +17,19 @@
|
||||
</section>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, toValue } from 'vue'
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import TextButton from '@/components/button/TextButton.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { cancelText, confirmText, confirmClass } = defineProps<{
|
||||
const { cancelText, confirmText, confirmClass, optionsDisabled } = defineProps<{
|
||||
cancelText?: string
|
||||
confirmText?: string
|
||||
confirmClass?: string
|
||||
optionsDisabled?: MaybeRefOrGetter<boolean>
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
@@ -35,4 +39,5 @@ defineEmits<{
|
||||
|
||||
const confirmTextX = computed(() => confirmText || t('g.confirm'))
|
||||
const cancelTextX = computed(() => cancelText || t('g.cancel'))
|
||||
const disabled = computed(() => toValue(optionsDisabled))
|
||||
</script>
|
||||
|
||||
@@ -163,6 +163,7 @@ export const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: navigator.language.split('-')[0] || 'en',
|
||||
fallbackLocale: 'en',
|
||||
escapeParameter: true,
|
||||
messages,
|
||||
// Ignore warnings for locale options as each option is in its own language.
|
||||
// e.g. "English", "中文", "Русский", "日本語", "한국어", "Français", "Español"
|
||||
|
||||
@@ -2154,7 +2154,10 @@
|
||||
},
|
||||
"deletion": {
|
||||
"header": "Delete this model?",
|
||||
"body": "This model will be permanently removed from your library."
|
||||
"body": "This model will be permanently removed from your library.",
|
||||
"inProgress": "Deleting {asset}...",
|
||||
"complete": "{asset} has been deleted.",
|
||||
"failed": "{asset} could not deleted."
|
||||
}
|
||||
},
|
||||
"mediaAsset": {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="!deletedLocal"
|
||||
data-component-id="AssetCard"
|
||||
:data-asset-id="asset.id"
|
||||
:aria-labelledby="titleId"
|
||||
:aria-describedby="descId"
|
||||
tabindex="0"
|
||||
:tabindex="interactive ? 0 : -1"
|
||||
:class="
|
||||
cn(
|
||||
'rounded-2xl overflow-hidden transition-all duration-200 bg-modal-card-background p-2 gap-2 flex flex-col h-full',
|
||||
@@ -24,6 +25,7 @@
|
||||
<img
|
||||
v-else
|
||||
:src="asset.preview_url"
|
||||
:alt="asset.name"
|
||||
class="size-full object-contain cursor-pointer"
|
||||
role="button"
|
||||
@click.self="interactive && $emit('select', asset)"
|
||||
@@ -154,7 +156,8 @@ const titleId = useId()
|
||||
const descId = useId()
|
||||
|
||||
const isEditing = ref(false)
|
||||
const newNameRef = ref<string>() // TEMPORARY: Replace with actual response from API
|
||||
const newNameRef = ref<string>()
|
||||
const deletedLocal = ref(false)
|
||||
|
||||
const tooltipDelay = computed<number>(() =>
|
||||
settingStore.get('LiteGraph.Node.TooltipDelay')
|
||||
@@ -167,12 +170,14 @@ const { isLoading, error } = useImage({
|
||||
|
||||
function confirmDeletion() {
|
||||
dropdownMenuButton.value?.hide()
|
||||
const promptText = ref<string>(t('assetBrowser.deletion.body'))
|
||||
const optionsDisabled = ref(false)
|
||||
const confirmDialog = showConfirmDialog({
|
||||
headerProps: {
|
||||
title: t('assetBrowser.deletion.header')
|
||||
},
|
||||
props: {
|
||||
promptText: t('assetBrowser.deletion.body')
|
||||
promptText
|
||||
},
|
||||
footerProps: {
|
||||
confirmText: t('g.delete'),
|
||||
@@ -180,17 +185,32 @@ function confirmDeletion() {
|
||||
confirmClass: cn(
|
||||
'bg-danger-200 text-base-foreground hover:bg-danger-200/80 focus:bg-danger-200/80 focus:ring ring-base-foreground'
|
||||
),
|
||||
optionsDisabled,
|
||||
onCancel: () => {
|
||||
closeDialog(confirmDialog)
|
||||
},
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
promptText.value = t('assetBrowser.deletion.inProgress', {
|
||||
asset: asset.name
|
||||
})
|
||||
await assetService.deleteAsset(asset.id)
|
||||
// TODO: Remove this from the list on success.
|
||||
promptText.value = t('assetBrowser.deletion.complete', {
|
||||
asset: asset.name
|
||||
})
|
||||
// Give a second for the completion message
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000))
|
||||
deletedLocal.value = true
|
||||
} catch (err: unknown) {
|
||||
console.error(err)
|
||||
promptText.value = t('assetBrowser.deletion.failed', {
|
||||
asset: asset.name
|
||||
})
|
||||
// Give a second for the completion message
|
||||
await new Promise((resolve) => setTimeout(resolve, 3_000))
|
||||
} finally {
|
||||
closeDialog(confirmDialog)
|
||||
}
|
||||
closeDialog(confirmDialog)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -204,10 +224,13 @@ function startAssetRename() {
|
||||
async function assetRename(newName?: string) {
|
||||
isEditing.value = false
|
||||
if (newName) {
|
||||
await assetService.updateAsset(asset.id, {
|
||||
// Optimistic update
|
||||
newNameRef.value = newName
|
||||
const result = await assetService.updateAsset(asset.id, {
|
||||
name: newName
|
||||
})
|
||||
newNameRef.value = newName
|
||||
// Update with the actual name once the server responds
|
||||
newNameRef.value = result.name
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
import { st } from '@/i18n'
|
||||
import { assetResponseSchema } from '@/platform/assets/schemas/assetSchema'
|
||||
import {
|
||||
assetItemSchema,
|
||||
assetResponseSchema
|
||||
} from '@/platform/assets/schemas/assetSchema'
|
||||
import type {
|
||||
AssetItem,
|
||||
AssetMetadata,
|
||||
@@ -287,13 +290,13 @@ function createAssetService() {
|
||||
*
|
||||
* @param id - The asset ID (UUID)
|
||||
* @param newData - The data to update
|
||||
* @returns Promise<void>
|
||||
* @returns Promise<AssetItem>
|
||||
* @throws Error if update fails
|
||||
*/
|
||||
async function updateAsset(
|
||||
id: string,
|
||||
newData: Partial<AssetMetadata>
|
||||
): Promise<string> {
|
||||
): Promise<AssetItem> {
|
||||
const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
@@ -307,7 +310,15 @@ function createAssetService() {
|
||||
`Unable to update asset ${id}: Server returned ${res.status}`
|
||||
)
|
||||
}
|
||||
return await res.json()
|
||||
|
||||
const newAsset = assetItemSchema.safeParse(await res.json())
|
||||
if (newAsset.success) {
|
||||
return newAsset.data
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unable to update asset ${id}: Invalid response - ${newAsset.error}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user