feat(frontend): inline tag rename, merge confirm dialog, TagsView wiring

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 13:42:46 -04:00
parent 1d1574e586
commit a50701ee6d
3 changed files with 115 additions and 7 deletions
@@ -0,0 +1,40 @@
<template>
<v-dialog :model-value="modelValue" max-width="460" @update:model-value="$emit('cancel')">
<v-card>
<v-card-title>Merge tags</v-card-title>
<v-card-text v-if="collision">
<p>
Merge <strong>{{ collision.sourceName }}</strong> into
<strong>{{ collision.target.name }}</strong>? This moves
<strong>{{ collision.sourceImageCount }}</strong>
image(s) onto <strong>{{ collision.target.name }}</strong>.
</p>
<p class="mt-2">
<template v-if="collision.willAlias">
{{ collision.sourceName }} will be kept as an alias of
{{ collision.target.name }} the tagger may still predict it.
</template>
<template v-else>
{{ collision.sourceName }} will be permanently deleted.
</template>
</p>
<p class="mt-2 text-medium-emphasis">This can't be undone.</p>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="$emit('cancel')">Cancel</v-btn>
<v-btn color="accent" variant="flat" @click="$emit('confirm')">
Merge
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup>
defineProps({
modelValue: { type: Boolean, default: false },
collision: { type: Object, default: null }
})
defineEmits(['confirm', 'cancel'])
</script>
+53 -6
View File
@@ -1,5 +1,5 @@
<template>
<v-card class="fc-tagcard" @click="$emit('open', card.id)">
<v-card class="fc-tagcard" @click="onCardClick">
<div class="fc-tagcard__previews">
<img
v-for="(t, i) in card.preview_thumbnails" :key="i"
@@ -11,9 +11,25 @@
</div>
<v-card-text class="fc-tagcard__body">
<div class="fc-tagcard__name">
{{ card.name }}
<span v-if="card.kind === 'character' && card.fandom_name"
class="fc-tagcard__fandom"> {{ card.fandom_name }}</span>
<template v-if="!editing">
{{ card.name }}
<span v-if="card.kind === 'character' && card.fandom_name"
class="fc-tagcard__fandom"> {{ card.fandom_name }}</span>
<v-icon
class="fc-tagcard__edit" size="14" icon="mdi-pencil"
@click.stop="startEdit"
/>
</template>
<v-text-field
v-else
v-model="draft"
density="compact" variant="outlined" hide-details
autofocus
@click.stop
@keydown.stop.enter="submit"
@keydown.stop.esc="cancel"
@blur="cancel"
/>
</div>
<div class="fc-tagcard__meta">
<v-chip size="x-small" label>{{ card.kind }}</v-chip>
@@ -24,8 +40,31 @@
</template>
<script setup>
defineProps({ card: { type: Object, required: true } })
defineEmits(['open'])
import { ref } from 'vue'
const props = defineProps({ card: { type: Object, required: true } })
const emit = defineEmits(['open', 'rename'])
const editing = ref(false)
const draft = ref('')
function onCardClick() {
if (!editing.value) emit('open', props.card.id)
}
function startEdit() {
draft.value = props.card.name
editing.value = true
}
function cancel() {
editing.value = false
}
function submit() {
const name = draft.value.trim()
editing.value = false
if (name && name !== props.card.name) {
emit('rename', { id: props.card.id, name })
}
}
</script>
<style scoped>
@@ -48,4 +87,12 @@ defineEmits(['open'])
margin-top: 4px;
}
.fc-tagcard__count { font-variant-numeric: tabular-nums; opacity: 0.8; }
.fc-tagcard__edit {
opacity: 0;
margin-left: 6px;
cursor: pointer;
transition: opacity .15s ease;
}
.fc-tagcard:hover .fc-tagcard__edit { opacity: .6; }
.fc-tagcard__edit:hover { opacity: 1; }
</style>