refactor(ui): CardHeading primitive for icon+title card/dialog headings (DRY pattern sweep)
The icon+title v-card-title heading (d-flex align-center + gap + <v-icon size=small> + <span>) was hand-rolled identically in 13 cards/dialogs (15 heading instances). Consolidate to <CardHeading icon title> (components/common) with an iconColor prop (error headings) and a default slot for trailing content (spacer+actions, inline status chip). Adopted everywhere the pattern appears — all-or-nothing per the hardened DRY process. Over-DRY guard: plain text-only <v-card-title> one-liners are NOT this pattern and stay; DownloadDetailModal leads with a status CHIP (not an icon), a different concept, left alone. §8b: the only remaining d-flex align-center v-card-title is that intentional variant. Catalog updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-danger-zone mt-8" variant="outlined">
|
<v-card class="fc-danger-zone mt-8" variant="outlined">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-alert-octagon" icon-color="error" title="Danger zone" />
|
||||||
<v-icon icon="mdi-alert-octagon" color="error" size="small" />
|
|
||||||
<span>Danger zone</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="text-body-2 fc-muted mb-4">
|
<p class="text-body-2 fc-muted mb-4">
|
||||||
Cascade-delete this artist and every image, source, post, and
|
Cascade-delete this artist and every image, source, post, and
|
||||||
@@ -37,6 +34,7 @@ import { useRouter } from 'vue-router'
|
|||||||
|
|
||||||
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
import { useAdminStore } from '../../stores/admin.js'
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
slug: { type: String, required: true },
|
slug: { type: String, required: true },
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-clean-card">
|
<v-card class="fc-clean-card">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-image-size-select-small" title="Minimum dimensions" />
|
||||||
<v-icon icon="mdi-image-size-select-small" size="small" />
|
|
||||||
<span>Minimum dimensions</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-3">
|
<p class="fc-muted text-body-2 mb-3">
|
||||||
Find and delete images smaller than the threshold. Mirrors the
|
Find and delete images smaller than the threshold. Mirrors the
|
||||||
@@ -67,6 +64,7 @@ import { onMounted, ref } from 'vue'
|
|||||||
|
|
||||||
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
import { useCleanupStore } from '../../stores/cleanup.js'
|
import { useCleanupStore } from '../../stores/cleanup.js'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
// Backend's preview response hands the full Tier-C confirm token back
|
// Backend's preview response hands the full Tier-C confirm token back
|
||||||
// as `confirm_token` (e.g. `delete-min-dim-1a2b3c4d`); passed straight
|
// as `confirm_token` (e.g. `delete-min-dim-1a2b3c4d`); passed straight
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-clean-card">
|
<v-card class="fc-clean-card">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-palette-swatch" title="Single-color audit" />
|
||||||
<v-icon icon="mdi-palette-swatch" size="small" />
|
|
||||||
<span>Single-color audit</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-3">
|
<p class="fc-muted text-body-2 mb-3">
|
||||||
Scan library for images dominated by one color within the
|
Scan library for images dominated by one color within the
|
||||||
@@ -97,6 +94,7 @@ import { toast } from '../../utils/toast.js'
|
|||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
import { useCleanupStore } from '../../stores/cleanup.js'
|
import { useCleanupStore } from '../../stores/cleanup.js'
|
||||||
|
|
||||||
const store = useCleanupStore()
|
const store = useCleanupStore()
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-clean-card">
|
<v-card class="fc-clean-card">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-checkerboard" title="Transparency audit" />
|
||||||
<v-icon icon="mdi-checkerboard" size="small" />
|
|
||||||
<span>Transparency audit</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-3">
|
<p class="fc-muted text-body-2 mb-3">
|
||||||
Scan library for images whose transparent-pixel fraction exceeds
|
Scan library for images whose transparent-pixel fraction exceeds
|
||||||
@@ -84,6 +81,7 @@ import { toast } from '../../utils/toast.js'
|
|||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
import { useCleanupStore } from '../../stores/cleanup.js'
|
import { useCleanupStore } from '../../stores/cleanup.js'
|
||||||
|
|
||||||
const store = useCleanupStore()
|
const store = useCleanupStore()
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Canonical card/dialog heading: an optional leading icon + the title, with
|
||||||
|
a default slot for trailing content (a v-spacer + actions, or an inline
|
||||||
|
status chip). The icon+title v-card-title row was hand-rolled identically
|
||||||
|
in 14 cards/dialogs (DRY pattern sweep 2026-06-09). Plain text-only
|
||||||
|
v-card-titles are NOT this pattern and keep using <v-card-title> directly. -->
|
||||||
|
<v-card-title class="d-flex align-center fc-card-heading">
|
||||||
|
<v-icon v-if="icon" :icon="icon" :color="iconColor || undefined" size="small" />
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<slot />
|
||||||
|
</v-card-title>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
// e.g. 'error' for danger/error headings; '' keeps the default text color.
|
||||||
|
iconColor: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fc-card-heading { gap: 10px; }
|
||||||
|
</style>
|
||||||
@@ -2,14 +2,15 @@
|
|||||||
<v-dialog :model-value="modelValue" max-width="900"
|
<v-dialog :model-value="modelValue" max-width="900"
|
||||||
@update:model-value="$emit('update:modelValue', $event)">
|
@update:model-value="$emit('update:modelValue', $event)">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="d-flex align-center" style="gap: 12px;">
|
<CardHeading
|
||||||
<v-icon icon="mdi-alert-circle-outline" color="error" />
|
icon="mdi-alert-circle-outline" icon-color="error"
|
||||||
<span>{{ displayTitle }}</span>
|
:title="displayTitle"
|
||||||
|
>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn icon variant="text" size="small" @click="close">
|
<v-btn icon variant="text" size="small" @click="close">
|
||||||
<v-icon>mdi-close</v-icon>
|
<v-icon>mdi-close</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<dl v-if="contextRows.length" class="fc-err-context">
|
<dl v-if="contextRows.length" class="fc-err-context">
|
||||||
<template v-for="(row, idx) in contextRows" :key="idx">
|
<template v-for="(row, idx) in contextRows" :key="idx">
|
||||||
@@ -37,6 +38,7 @@ import { toast } from '../../utils/toast.js'
|
|||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { copyText } from '../../utils/clipboard.js'
|
import { copyText } from '../../utils/clipboard.js'
|
||||||
|
import CardHeading from './CardHeading.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Boolean, default: false },
|
modelValue: { type: Boolean, default: false },
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-backup-card">
|
<v-card class="fc-backup-card">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-database-export" title="Backups" />
|
||||||
<v-icon icon="mdi-database-export" size="small" />
|
|
||||||
<span>Backups</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-4">
|
<p class="fc-muted text-body-2 mb-4">
|
||||||
pg_dump for the database (fast, nightly-schedulable);
|
pg_dump for the database (fast, nightly-schedulable);
|
||||||
@@ -101,6 +98,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|||||||
|
|
||||||
import { useBackupStore } from '../../stores/backup.js'
|
import { useBackupStore } from '../../stores/backup.js'
|
||||||
import BackupConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
import BackupConfirmModal from '../modal/DestructiveConfirmModal.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
import BackupRunsTable from './BackupRunsTable.vue'
|
import BackupRunsTable from './BackupRunsTable.vue'
|
||||||
|
|
||||||
const store = useBackupStore()
|
const store = useBackupStore()
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-ext-card">
|
<v-card class="fc-ext-card">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-puzzle" title="Browser extension">
|
||||||
<v-icon icon="mdi-puzzle" size="small" />
|
|
||||||
<span>Browser extension</span>
|
|
||||||
<span v-if="manifest?.installed" class="text-caption fc-muted">
|
<span v-if="manifest?.installed" class="text-caption fc-muted">
|
||||||
· Firefox · v{{ manifest.version }}
|
· Firefox · v{{ manifest.version }}
|
||||||
</span>
|
</span>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2">
|
<p class="fc-muted text-body-2">
|
||||||
@@ -112,6 +110,7 @@ import { toast } from '../../utils/toast.js'
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useApi } from '../../composables/useApi.js'
|
import { useApi } from '../../composables/useApi.js'
|
||||||
import { copyText } from '../../utils/clipboard.js'
|
import { copyText } from '../../utils/clipboard.js'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="d-flex align-center" style="gap: 12px;">
|
<CardHeading title="Recent import tasks">
|
||||||
<span>Recent import tasks</span>
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-select
|
<v-select
|
||||||
v-model="statusFilter" :items="statusOptions" density="compact"
|
v-model="statusFilter" :items="statusOptions" density="compact"
|
||||||
@@ -29,7 +28,7 @@
|
|||||||
>
|
>
|
||||||
Clear completed…
|
Clear completed…
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
<v-data-table-virtual
|
<v-data-table-virtual
|
||||||
:headers="headers" :items="store.tasks" :loading="store.tasksLoading"
|
:headers="headers" :items="store.tasks" :loading="store.tasksLoading"
|
||||||
height="480" density="compact" no-data-text="No tasks yet — trigger a scan above."
|
height="480" density="compact" no-data-text="No tasks yet — trigger a scan above."
|
||||||
@@ -129,6 +128,7 @@ import { toast } from '../../utils/toast.js'
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useImportStore } from '../../stores/import.js'
|
import { useImportStore } from '../../stores/import.js'
|
||||||
import ErrorDetailModal from '../common/ErrorDetailModal.vue'
|
import ErrorDetailModal from '../common/ErrorDetailModal.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const store = useImportStore()
|
const store = useImportStore()
|
||||||
const statusFilter = ref(null)
|
const statusFilter = ref(null)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-post-maint">
|
<v-card class="fc-post-maint">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-post-outline" title="Post maintenance" />
|
||||||
<v-icon icon="mdi-post-outline" size="small" />
|
|
||||||
<span>Post maintenance</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-4">
|
<p class="fc-muted text-body-2 mb-4">
|
||||||
Remove <strong>bare posts</strong> — post rows with no images (neither
|
Remove <strong>bare posts</strong> — post rows with no images (neither
|
||||||
@@ -61,6 +58,7 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
import { useAdminStore } from '../../stores/admin.js'
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const store = useAdminStore()
|
const store = useAdminStore()
|
||||||
const preview = ref(null)
|
const preview = ref(null)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-activity-summary">
|
<v-card class="fc-activity-summary">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-pulse" title="System activity (last min)">
|
||||||
<v-icon icon="mdi-pulse" size="small" />
|
|
||||||
<span>System activity (last min)</span>
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="text" size="small" rounded="pill"
|
variant="text" size="small" rounded="pill"
|
||||||
@@ -11,7 +9,7 @@
|
|||||||
View activity
|
View activity
|
||||||
<v-icon end size="small">mdi-arrow-right</v-icon>
|
<v-icon end size="small">mdi-arrow-right</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-alert
|
<v-alert
|
||||||
@@ -36,6 +34,7 @@ import { onMounted, onUnmounted } from 'vue'
|
|||||||
|
|
||||||
import { useSystemActivityStore } from '../../stores/systemActivity.js'
|
import { useSystemActivityStore } from '../../stores/systemActivity.js'
|
||||||
import QueuesTable from './QueuesTable.vue'
|
import QueuesTable from './QueuesTable.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
defineEmits(['open-activity'])
|
defineEmits(['open-activity'])
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
<div class="fc-activity">
|
<div class="fc-activity">
|
||||||
<!-- Queues + workers pane -->
|
<!-- Queues + workers pane -->
|
||||||
<v-card class="mb-4">
|
<v-card class="mb-4">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-pulse" title="Queues + workers">
|
||||||
<v-icon icon="mdi-pulse" size="small" />
|
|
||||||
<span>Queues + workers</span>
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<span class="text-caption fc-muted">
|
<span class="text-caption fc-muted">
|
||||||
updated {{ formatRelative(store.queues?.fetched_at) }}
|
updated {{ formatRelative(store.queues?.fetched_at) }}
|
||||||
</span>
|
</span>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<QueuesTable
|
<QueuesTable
|
||||||
:queues="store.queues"
|
:queues="store.queues"
|
||||||
@@ -21,14 +19,12 @@
|
|||||||
|
|
||||||
<!-- Recent failures pane -->
|
<!-- Recent failures pane -->
|
||||||
<v-card class="mb-4">
|
<v-card class="mb-4">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-alert-circle-outline" title="Recent failures (last 24h)">
|
||||||
<v-icon icon="mdi-alert-circle-outline" size="small" />
|
|
||||||
<span>Recent failures (last 24h)</span>
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<span class="text-caption fc-muted">
|
<span class="text-caption fc-muted">
|
||||||
{{ failureCount }} failures
|
{{ failureCount }} failures
|
||||||
</span>
|
</span>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div v-if="errorTypes.length" class="mb-3 fc-pills">
|
<div v-if="errorTypes.length" class="mb-3 fc-pills">
|
||||||
<v-chip
|
<v-chip
|
||||||
@@ -79,9 +75,7 @@
|
|||||||
|
|
||||||
<!-- All activity pane -->
|
<!-- All activity pane -->
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-format-list-bulleted" title="All recent activity">
|
||||||
<v-icon icon="mdi-format-list-bulleted" size="small" />
|
|
||||||
<span>All recent activity</span>
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-select
|
<v-select
|
||||||
v-model="filterQueue"
|
v-model="filterQueue"
|
||||||
@@ -102,7 +96,7 @@
|
|||||||
<v-icon start size="small">mdi-refresh</v-icon>
|
<v-icon start size="small">mdi-refresh</v-icon>
|
||||||
Refresh
|
Refresh
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-title>
|
</CardHeading>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-table density="compact">
|
<v-table density="compact">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -157,6 +151,7 @@ import { useSystemActivityStore } from '../../stores/systemActivity.js'
|
|||||||
import { formatRelative as fmtRelative } from '../../utils/date.js'
|
import { formatRelative as fmtRelative } from '../../utils/date.js'
|
||||||
import ErrorDetailModal from '../common/ErrorDetailModal.vue'
|
import ErrorDetailModal from '../common/ErrorDetailModal.vue'
|
||||||
import QueuesTable from './QueuesTable.vue'
|
import QueuesTable from './QueuesTable.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
// Click-to-open modal for full error text. Replaces the unusable
|
// Click-to-open modal for full error text. Replaces the unusable
|
||||||
// :title="..." tooltip (operator-flagged 2026-05-26: SQLAlchemy
|
// :title="..." tooltip (operator-flagged 2026-05-26: SQLAlchemy
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="fc-tag-maint">
|
<v-card class="fc-tag-maint">
|
||||||
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
<CardHeading icon="mdi-tag-remove" title="Tag maintenance" />
|
||||||
<v-icon icon="mdi-tag-remove" size="small" />
|
|
||||||
<span>Tag maintenance</span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="fc-muted text-body-2 mb-4">
|
<p class="fc-muted text-body-2 mb-4">
|
||||||
Remove tags with zero image associations and zero series-page
|
Remove tags with zero image associations and zero series-page
|
||||||
@@ -196,6 +193,7 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
import { useAdminStore } from '../../stores/admin.js'
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
import SampleNameGrid from '../common/SampleNameGrid.vue'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const store = useAdminStore()
|
const store = useAdminStore()
|
||||||
const preview = ref(null)
|
const preview = ref(null)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
@update:model-value="$emit('update:modelValue', $event)"
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
>
|
>
|
||||||
<v-card v-if="source">
|
<v-card v-if="source">
|
||||||
<v-card-title class="d-flex align-center">
|
<CardHeading
|
||||||
<v-icon class="mr-2">mdi-eye-outline</v-icon>
|
icon="mdi-eye-outline"
|
||||||
Preview · {{ source.artist_name }}
|
:title="`Preview · ${source.artist_name}`"
|
||||||
</v-card-title>
|
/>
|
||||||
<v-card-subtitle class="fc-preview__url">{{ source.url }}</v-card-subtitle>
|
<v-card-subtitle class="fc-preview__url">{{ source.url }}</v-card-subtitle>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useSourcesStore } from '../../stores/sources.js'
|
import { useSourcesStore } from '../../stores/sources.js'
|
||||||
import { formatRelative } from '../../utils/date.js'
|
import { formatRelative } from '../../utils/date.js'
|
||||||
|
import CardHeading from '../common/CardHeading.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Boolean, default: false },
|
modelValue: { type: Boolean, default: false },
|
||||||
|
|||||||
Reference in New Issue
Block a user