refactor(ui): SampleNameGrid primitive for maintenance-card previews (DRY pattern sweep)
The preview sample-name grid (scrollable monospace chip grid) was hand-rolled 5 times with verbatim-duplicated markup + CSS — TagMaintenanceCard (×4) and PostMaintenanceCard. Consolidate to <SampleNameGrid> (components/common): pass :names for the plain case, default slot for the normalize from→to chips (styled via :slotted .fc-name). Removed the duplicated .fc-name-grid/.fc-name CSS from both cards. Over-DRY guard: only the verbatim-duplicated grid is merged — each card's preview/commit logic and result-count lines genuinely differ and stay put; MinDimensionCard's typed-token confirm is a separate variant, untouched. §8b: fc-name-grid now lives only in SampleNameGrid. Catalog updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Scrollable grid of monospace name chips — the preview "here's a sample of
|
||||||
|
what will change" surface shared by the maintenance cards (DRY pattern
|
||||||
|
sweep 2026-06-09). Pass `names` for the common case; use the default slot
|
||||||
|
to render custom chips (style them `.fc-name` and they inherit the look
|
||||||
|
via :slotted). The caller keeps its own v-if for the empty case. -->
|
||||||
|
<div class="fc-name-grid">
|
||||||
|
<slot>
|
||||||
|
<span v-for="(n, i) in names" :key="i" class="fc-name">{{ n }}</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
names: { type: Array, default: () => [] },
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fc-name-grid {
|
||||||
|
display: flex; flex-wrap: wrap; gap: 4px 8px;
|
||||||
|
max-height: 200px; overflow-y: auto;
|
||||||
|
padding: 8px; border-radius: 4px;
|
||||||
|
background: rgb(var(--v-theme-surface-light));
|
||||||
|
}
|
||||||
|
.fc-name, :slotted(.fc-name) {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -36,11 +36,10 @@
|
|||||||
Nothing to clean up.
|
Nothing to clean up.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="preview.sample_names?.length" class="fc-name-grid mb-3">
|
<SampleNameGrid
|
||||||
<span v-for="(n, i) in preview.sample_names" :key="i" class="fc-name">
|
v-if="preview.sample_names?.length"
|
||||||
{{ n }}
|
:names="preview.sample_names" class="mb-3"
|
||||||
</span>
|
/>
|
||||||
</div>
|
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error" variant="flat" rounded="pill"
|
color="error" variant="flat" rounded="pill"
|
||||||
prepend-icon="mdi-delete-sweep"
|
prepend-icon="mdi-delete-sweep"
|
||||||
@@ -61,6 +60,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useAdminStore } from '../../stores/admin.js'
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
|
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
||||||
|
|
||||||
const store = useAdminStore()
|
const store = useAdminStore()
|
||||||
const preview = ref(null)
|
const preview = ref(null)
|
||||||
@@ -94,15 +94,4 @@ async function onCommit() {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-post-maint { border-radius: 8px; }
|
.fc-post-maint { border-radius: 8px; }
|
||||||
.fc-name-grid {
|
|
||||||
display: flex; flex-wrap: wrap; gap: 4px 8px;
|
|
||||||
max-height: 200px; overflow-y: auto;
|
|
||||||
padding: 8px; border-radius: 4px;
|
|
||||||
background: rgb(var(--v-theme-surface-light));
|
|
||||||
}
|
|
||||||
.fc-name {
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgb(var(--v-theme-on-surface-variant));
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,11 +31,10 @@
|
|||||||
Showing first 50 names.
|
Showing first 50 names.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="preview.sample_names?.length" class="fc-name-grid mb-3">
|
<SampleNameGrid
|
||||||
<span v-for="n in preview.sample_names" :key="n" class="fc-name">
|
v-if="preview.sample_names?.length"
|
||||||
{{ n }}
|
:names="preview.sample_names" class="mb-3"
|
||||||
</span>
|
/>
|
||||||
</div>
|
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error" variant="flat" rounded="pill"
|
color="error" variant="flat" rounded="pill"
|
||||||
prepend-icon="mdi-delete-sweep"
|
prepend-icon="mdi-delete-sweep"
|
||||||
@@ -76,11 +75,10 @@
|
|||||||
{{ p }}: {{ n }}
|
{{ p }}: {{ n }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="kindPreview.sample_names?.length" class="fc-name-grid mb-3">
|
<SampleNameGrid
|
||||||
<span v-for="n in kindPreview.sample_names" :key="n" class="fc-name">
|
v-if="kindPreview.sample_names?.length"
|
||||||
{{ n }}
|
:names="kindPreview.sample_names" class="mb-3"
|
||||||
</span>
|
/>
|
||||||
</div>
|
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error" variant="flat" rounded="pill"
|
color="error" variant="flat" rounded="pill"
|
||||||
prepend-icon="mdi-delete-sweep"
|
prepend-icon="mdi-delete-sweep"
|
||||||
@@ -122,11 +120,10 @@
|
|||||||
across <strong>{{ resetPreview.applications }}</strong> image
|
across <strong>{{ resetPreview.applications }}</strong> image
|
||||||
application(s).
|
application(s).
|
||||||
</p>
|
</p>
|
||||||
<div v-if="resetPreview.sample_names?.length" class="fc-name-grid mb-3">
|
<SampleNameGrid
|
||||||
<span v-for="n in resetPreview.sample_names" :key="n" class="fc-name">
|
v-if="resetPreview.sample_names?.length"
|
||||||
{{ n }}
|
:names="resetPreview.sample_names" class="mb-3"
|
||||||
</span>
|
/>
|
||||||
</div>
|
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error" variant="flat" rounded="pill"
|
color="error" variant="flat" rounded="pill"
|
||||||
prepend-icon="mdi-delete-alert"
|
prepend-icon="mdi-delete-alert"
|
||||||
@@ -170,14 +167,14 @@
|
|||||||
<strong>{{ normPreview.collisions }}</strong> collision(s) merging
|
<strong>{{ normPreview.collisions }}</strong> collision(s) merging
|
||||||
<strong>{{ normPreview.tags_to_merge }}</strong> tag(s) away.
|
<strong>{{ normPreview.tags_to_merge }}</strong> tag(s) away.
|
||||||
</p>
|
</p>
|
||||||
<div v-if="normPreview.sample?.length" class="fc-name-grid mb-3">
|
<SampleNameGrid v-if="normPreview.sample?.length" class="mb-3">
|
||||||
<span
|
<span
|
||||||
v-for="s in normPreview.sample" :key="s.to + s.kind"
|
v-for="s in normPreview.sample" :key="s.to + s.kind"
|
||||||
class="fc-name"
|
class="fc-name"
|
||||||
>
|
>
|
||||||
<template v-if="s.merge">{{ s.from.join(' + ') }} → </template>{{ s.to }}
|
<template v-if="s.merge">{{ s.from.join(' + ') }} → </template>{{ s.to }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</SampleNameGrid>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="error" variant="flat" rounded="pill"
|
color="error" variant="flat" rounded="pill"
|
||||||
prepend-icon="mdi-format-letter-case"
|
prepend-icon="mdi-format-letter-case"
|
||||||
@@ -198,6 +195,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useAdminStore } from '../../stores/admin.js'
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
|
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
||||||
|
|
||||||
const store = useAdminStore()
|
const store = useAdminStore()
|
||||||
const preview = ref(null)
|
const preview = ref(null)
|
||||||
@@ -304,15 +302,4 @@ async function onNormCommit() {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-tag-maint { border-radius: 8px; }
|
.fc-tag-maint { border-radius: 8px; }
|
||||||
.fc-name-grid {
|
|
||||||
display: flex; flex-wrap: wrap; gap: 4px 8px;
|
|
||||||
max-height: 200px; overflow-y: auto;
|
|
||||||
padding: 8px; border-radius: 4px;
|
|
||||||
background: rgb(var(--v-theme-surface-light));
|
|
||||||
}
|
|
||||||
.fc-name {
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgb(var(--v-theme-on-surface-variant));
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user