feat: improve recipe preview image UI with replace/remove buttons

Replace drop-zone with action buttons (Sostituisci/Rimuovi) when image
exists, matching the task editor pattern. Add upload overlay with
spinner on the image during file upload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-20 19:10:59 +01:00
parent dbfb5591c5
commit ecf700eadf
+85 -57
View File
@@ -258,69 +258,97 @@
<div class="tmf-card-body space-y-4"> <div class="tmf-card-body space-y-4">
<!-- Current image preview --> <!-- Current image preview -->
<div x-show="currentFilePath" <template x-if="currentFilePath">
class="relative rounded-lg overflow-hidden bg-[var(--bg-secondary)] border border-[var(--border-color)]"> <div class="space-y-3">
<img x-bind:src="currentFilePath ? ('/maker/api/files/' + currentFilePath) : ''" <div class="relative rounded-lg overflow-hidden bg-[var(--bg-secondary)] border border-[var(--border-color)]">
class="block mx-auto max-h-64 object-contain p-2" <img x-bind:src="'/maker/api/files/' + currentFilePath"
loading="lazy"> class="block mx-auto max-h-64 object-contain p-2"
<!-- Remove button --> :class="{ 'opacity-40': uploadingFile }"
<button @click.stop="currentFilePath = ''" loading="lazy">
type="button" <!-- Upload overlay on image -->
class="absolute top-2 right-2 p-1.5 rounded-full bg-red-500/80 hover:bg-red-600 text-white transition-colors" <div x-show="uploadingFile"
title="{{ _('Rimuovi immagine') }}"> class="absolute inset-0 flex flex-col items-center justify-center bg-white/60 dark:bg-black/50">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <svg class="w-8 h-8 animate-spin text-primary" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
</svg> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
</button> </svg>
</div> <span class="text-sm font-medium text-[var(--text-primary)] mt-2">{{ _('Caricamento in corso...') }}</span>
</div>
<!-- Upload area --> </div>
<div class="drop-zone p-6 text-center cursor-pointer relative" <!-- Action buttons -->
:class="{ 'drag-over': dragOver }" <div class="flex items-center gap-2 flex-wrap">
@dragover.prevent="dragOver = true" <label class="btn btn-secondary text-sm gap-1.5 cursor-pointer"
@dragleave.prevent="dragOver = false" :class="{ 'opacity-50 pointer-events-none': uploadingFile }">
@drop.prevent="handleDrop($event)" <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
@click="$refs.fileInput.click()"> <path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
<input type="file" {{ _('Sostituisci') }}
x-ref="fileInput" <input type="file"
@change="uploadFile($event.target.files[0]); $event.target.value = ''" accept="image/*,application/pdf"
accept="image/*,application/pdf" class="hidden"
class="hidden"> :disabled="uploadingFile"
@change="uploadFile($event.target.files[0]); $event.target.value = ''">
<!-- Uploading spinner --> </label>
<div x-show="uploadingFile" class="flex flex-col items-center gap-3 py-4"> <button @click.stop="currentFilePath = ''"
<svg class="w-8 h-8 animate-spin text-primary" fill="none" viewBox="0 0 24 24"> type="button"
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/> :disabled="uploadingFile"
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/> class="btn btn-secondary text-sm gap-1.5 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20">
</svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<span class="text-sm text-[var(--text-secondary)]">{{ _('Caricamento in corso...') }}</span> <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
{{ _('Rimuovi') }}
</button>
</div>
</div> </div>
</template>
<!-- Upload prompt --> <!-- Upload area (only when no image) -->
<div x-show="!uploadingFile" class="flex flex-col items-center gap-3 py-4"> <template x-if="!currentFilePath">
<div class="w-12 h-12 rounded-full bg-primary-50 dark:bg-primary-900/30 flex items-center justify-center"> <div class="drop-zone p-6 text-center cursor-pointer relative"
<svg class="w-6 h-6 text-primary" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> :class="{ 'drag-over': dragOver }"
<path stroke-linecap="round" stroke-linejoin="round" @dragover.prevent="dragOver = true"
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/> @dragleave.prevent="dragOver = false"
@drop.prevent="handleDrop($event)"
@click="$refs.fileInput.click()">
<input type="file"
x-ref="fileInput"
@change="uploadFile($event.target.files[0]); $event.target.value = ''"
accept="image/*,application/pdf"
class="hidden">
<!-- Uploading spinner -->
<div x-show="uploadingFile" class="flex flex-col items-center gap-3 py-4">
<svg class="w-8 h-8 animate-spin text-primary" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
</svg> </svg>
<span class="text-sm text-[var(--text-secondary)]">{{ _('Caricamento in corso...') }}</span>
</div> </div>
<div class="text-center">
<span x-show="!currentFilePath" class="inline-block px-4 py-2 rounded-lg bg-primary text-white text-sm font-semibold mb-2"> <!-- Upload prompt -->
{{ _('Carica Immagine o PDF') }} <div x-show="!uploadingFile" class="flex flex-col items-center gap-3 py-4">
</span> <div class="w-12 h-12 rounded-full bg-primary-50 dark:bg-primary-900/30 flex items-center justify-center">
<span x-show="currentFilePath" class="inline-block px-4 py-2 rounded-lg bg-[var(--bg-tertiary)] text-[var(--text-primary)] text-sm font-semibold mb-2 border border-[var(--border-color)]"> <svg class="w-6 h-6 text-primary" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
{{ _('Sostituisci Immagine') }} <path stroke-linecap="round" stroke-linejoin="round"
</span> d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/>
<p class="text-xs text-[var(--text-muted)]"> </svg>
{{ _('Trascina qui oppure clicca. Formati: PNG, JPG, WebP, PDF (max 50MB)') }} </div>
</p> <div class="text-center">
<p class="text-xs text-[var(--text-muted)] mt-1"> <span class="inline-block px-4 py-2 rounded-lg bg-primary text-white text-sm font-semibold mb-2">
{{ _('Usata come thumbnail nella lista ricette') }} {{ _('Carica Immagine') }}
</p> </span>
<p class="text-xs text-[var(--text-muted)]">
{{ _('Trascina qui oppure clicca. Formati: PNG, JPG, WebP, PDF (max 50MB)') }}
</p>
<p class="text-xs text-[var(--text-muted)] mt-1">
{{ _('Usata come thumbnail nella lista ricette') }}
</p>
</div>
</div> </div>
</div> </div>
</div> </template>
</div> </div>
</div> </div>