- Galerien sind nun eine Entität - es kann mehrere geben
- Neues Sparkbooth-Upload-Feature: Endpoint /api/sparkbooth/upload (Token-basiert pro Galerie), Controller Api/SparkboothUploadController, Migration 2026_01_21_000001_add_upload_fields_to_galleries_table.php mit Upload-Flags/Token/Expiry;
Galerie-Modell und Factory/Seeder entsprechend erweitert.
- Filament: Neue Setup-Seite SparkboothSetup (mit View) zur schnellen Galerie- und Token-Erstellung inkl. QR/Endpoint/Snippet;
Galerie-Link-Views nutzen jetzt simple-qrcode (Composer-Dependency hinzugefügt) und bieten PNG-Download.
- Galerie-Tabelle: Slug/Pfad-Spalten entfernt, Action „Link-Details“ mit Modal; Created-at-Spalte hinzugefügt.
- Zugriffshärtung: Galerie-IDs in API (ImageController, Download/Print) geprüft; GalleryAccess/Middleware + Gallery-Modell/Slug-UUID
eingeführt; GalleryAccess-Inertia-Seite.
- UI/UX: LoadingSpinner/StyledImageDisplay verbessert, Delete-Confirm, Übersetzungen ergänzt.
This commit is contained in:
@@ -47,17 +47,17 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between gap-3 rounded-2xl border px-4 py-3 text-left font-semibold text-slate-900 transition focus:outline-none focus-visible:ring-2 dark:text-white"
|
||||
:class="[aiAvailable ? 'border-white/20 bg-white/40 hover:border-emerald-400 hover:bg-white/70 dark:border-white/10 dark:bg-white/5' : 'border-rose-200 bg-rose-50 cursor-not-allowed opacity-70']"
|
||||
:disabled="!aiAvailable"
|
||||
:class="[effectiveAiAvailable ? 'border-white/20 bg-white/40 hover:border-emerald-400 hover:bg-white/70 dark:border-white/10 dark:bg-white/5' : 'border-rose-200 bg-rose-50 cursor-not-allowed opacity-70']"
|
||||
:disabled="!effectiveAiAvailable"
|
||||
@click="showStyleSelectorView = true"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="h-2 w-2 rounded-full"
|
||||
:class="aiAvailable ? 'bg-emerald-500' : 'bg-rose-500'"></span>
|
||||
:class="effectiveAiAvailable ? 'bg-emerald-500' : 'bg-rose-500'"></span>
|
||||
<div>
|
||||
<p class="text-base">Stile anzeigen</p>
|
||||
<p class="text-sm font-normal text-slate-500 dark:text-slate-400">
|
||||
{{ aiAvailable ? 'Lass die KI dein Motiv verzaubern' : 'AI-Dienste derzeit nicht verfügbar' }}
|
||||
{{ effectiveAiAvailable ? 'Lass die KI dein Motiv verzaubern' : 'AI-Dienste derzeit nicht verfügbar' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,6 +126,9 @@
|
||||
<StyleSelector
|
||||
class="max-h-[520px] flex-1"
|
||||
:image_id="image?.id ?? image?.image_id"
|
||||
:allow-ai-styles="props.allowAiStyles"
|
||||
:gallery-slug="props.gallerySlug"
|
||||
:ai-available-override="props.aiAvailable"
|
||||
@styleSelected="handleStyleSelected"
|
||||
@close="$emit('close')"
|
||||
/>
|
||||
@@ -149,12 +152,31 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
allowAiStyles: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showPrintButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
gallerySlug: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
aiAvailable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['close', 'print', 'styleSelected', 'download']);
|
||||
|
||||
const settings = computed(() => page.props.settings || {});
|
||||
const showPrintButton = computed(() => settings.value.show_print_button ?? true);
|
||||
const showPrintButton = computed(() => {
|
||||
const fromSettings = settings.value.show_print_button ?? true;
|
||||
return fromSettings && props.showPrintButton;
|
||||
});
|
||||
|
||||
const shouldShowDownload = computed(() => {
|
||||
const hostname = window.location.hostname;
|
||||
@@ -166,23 +188,9 @@ const shouldShowDownload = computed(() => {
|
||||
});
|
||||
|
||||
const showStyleSelectorView = ref(false);
|
||||
const aiAvailable = ref(false);
|
||||
const effectiveAiAvailable = computed(() => props.allowAiStyles && props.aiAvailable);
|
||||
|
||||
const checkAiStatus = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/ai-status');
|
||||
aiAvailable.value = response.data.some(provider => provider.available);
|
||||
} catch (error) {
|
||||
console.error('Error checking AI status:', error);
|
||||
aiAvailable.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkAiStatus();
|
||||
// Check every 5 minutes
|
||||
setInterval(checkAiStatus, 300000);
|
||||
});
|
||||
onMounted(() => {});
|
||||
|
||||
watch(
|
||||
() => props.image,
|
||||
|
||||
Reference in New Issue
Block a user