Merge branch 'style-gallery-laravel12'

This commit is contained in:
soeren
2025-11-13 20:02:39 +01:00
11 changed files with 136 additions and 107 deletions

View File

@@ -5,7 +5,6 @@ namespace App\Http\Controllers;
use App\Models\Image; use App\Models\Image;
use App\Settings\GeneralSettings; use App\Settings\GeneralSettings;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Lang;
use Inertia\Inertia; use Inertia\Inertia;
class HomeController extends Controller class HomeController extends Controller
@@ -14,11 +13,6 @@ class HomeController extends Controller
public function index() public function index()
{ {
$locale = app()->getLocale();
$translations = array_merge(
Lang::get('api', [], $locale),
Lang::get('settings', [], $locale)
);
$galleryHeading = $this->settings->gallery_heading; $galleryHeading = $this->settings->gallery_heading;
$newImageTimespanMinutes = $this->settings->new_image_timespan_minutes; $newImageTimespanMinutes = $this->settings->new_image_timespan_minutes;
@@ -30,7 +24,6 @@ class HomeController extends Controller
}); });
return Inertia::render('Home', [ return Inertia::render('Home', [
'translations' => $translations,
'galleryHeading' => $galleryHeading, 'galleryHeading' => $galleryHeading,
'images' => $images, 'images' => $images,
]); ]);

View File

@@ -2,19 +2,19 @@
<div class="space-y-4"> <div class="space-y-4">
<p <p
v-if="!images.length" v-if="!images.length"
class="rounded-3xl border border-dashed border-white/20 bg-white/5 px-4 py-10 text-center text-sm text-slate-300" class="rounded-3xl border border-dashed border-slate-300 bg-white px-4 py-10 text-center text-sm text-slate-500 dark:border-white/20 dark:bg-white/5 dark:text-slate-300"
> >
{{ props.translations.empty_gallery || 'Noch keine Bilder vorhanden.' }} {{ __('api.gallery.empty') }}
</p> </p>
<div <div
v-else v-else
class="grid grid-cols-2 gap-4 sm:grid-cols-3 sm:gap-6 xl:grid-cols-4 2xl:grid-cols-5" class="grid grid-cols-2 gap-3 sm:grid-cols-3 sm:gap-5 xl:grid-cols-4 2xl:grid-cols-5"
> >
<button <button
v-for="image in images" v-for="image in images"
:key="image.id ?? image.name" :key="image.id ?? image.name"
type="button" type="button"
class="group relative aspect-[3/4] overflow-hidden rounded-[1.75rem] border border-white/10 bg-slate-900/30 shadow-2xl ring-1 ring-white/5 transition-all hover:border-emerald-300 focus:outline-none focus-visible:ring-4 focus-visible:ring-emerald-400" class="group relative aspect-[4/3] overflow-hidden rounded-[1.75rem] border border-slate-200 bg-white shadow-2xl ring-1 ring-slate-100 transition-all hover:border-emerald-300 focus:outline-none focus-visible:ring-4 focus-visible:ring-emerald-400 dark:border-white/10 dark:bg-slate-900/30 dark:ring-white/5"
@click="$emit('imageTapped', image, $event)" @click="$emit('imageTapped', image, $event)"
:aria-label="fallbackLabel(image)" :aria-label="fallbackLabel(image)"
> >
@@ -31,9 +31,9 @@
<span class="h-1.5 w-1.5 rounded-full bg-emerald-800"></span> <span class="h-1.5 w-1.5 rounded-full bg-emerald-800"></span>
{{ __('new') }} {{ __('new') }}
</span> </span>
<div class="pointer-events-none absolute inset-x-0 bottom-0 bg-gradient-to-t from-slate-950/70 via-slate-950/10 to-transparent p-4 text-left"> <div class="pointer-events-none absolute inset-x-0 bottom-0 bg-gradient-to-t from-slate-900/80 via-slate-900/10 to-transparent p-4 text-left text-white">
<p class="text-base font-semibold text-white drop-shadow">{{ fallbackLabel(image) }}</p> <p class="text-base font-semibold drop-shadow">{{ fallbackLabel(image) }}</p>
<p class="text-xs uppercase tracking-[0.3em] text-white/70">Tap to open</p> <p class="text-xs uppercase tracking-[0.3em] text-white/70">{{ __('api.gallery.tap_to_open') }}</p>
</div> </div>
</button> </button>
</div> </div>
@@ -48,17 +48,9 @@ const props = defineProps({
type: Array, type: Array,
required: true, required: true,
}, },
translations: {
type: Object,
required: true,
},
}); });
const emits = defineEmits(['imageTapped']); const emits = defineEmits(['imageTapped']);
const __ = (key) => { const fallbackLabel = (image) => image?.name || 'Bild';
return props.translations[key] || key;
};
const fallbackLabel = (image) => image?.name || props.translations.image || 'Bild';
</script> </script>

View File

@@ -2,7 +2,7 @@
<div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"> <div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg shadow-lg text-center flex flex-col items-center"> <div class="bg-white p-6 rounded-lg shadow-lg text-center flex flex-col items-center">
<div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div> <div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div>
<p class="text-gray-700 text-lg">{{ __('loading_spinner.processing_image') }}</p> <p class="text-gray-700 text-lg">{{ __('api.loading_spinner.processing_image') }}</p>
<p v-if="progress > 0" class="text-gray-700 text-sm mt-2">{{ progress }}%</p> <p v-if="progress > 0" class="text-gray-700 text-sm mt-2">{{ progress }}%</p>
</div> </div>
</div> </div>

View File

@@ -1,31 +1,31 @@
<template> <template>
<nav class="rounded-3xl border border-white/10 bg-white/5 p-5 text-white shadow-2xl backdrop-blur"> <nav class="rounded-3xl border border-slate-200 bg-white p-5 text-slate-900 shadow-2xl backdrop-blur transition-colors dark:border-white/10 dark:bg-white/5 dark:text-white">
<div class="flex flex-wrap items-center justify-between gap-4"> <div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-2 text-xs uppercase tracking-[0.4em] text-slate-300"> <div class="flex items-center gap-2 text-xs uppercase tracking-[0.4em] text-slate-500 dark:text-slate-300">
Seite Seite
<span class="text-xl font-semibold text-white">{{ currentPage }}</span> <span class="text-xl font-semibold text-slate-900 dark:text-white">{{ currentPage }}</span>
<span class="text-slate-500">/</span> <span class="text-slate-400 dark:text-slate-500">/</span>
<span class="text-lg text-slate-200">{{ totalPages }}</span> <span class="text-lg text-slate-700 dark:text-slate-200">{{ totalPages }}</span>
</div> </div>
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<button <button
type="button" type="button"
class="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-2 text-sm font-semibold text-white transition hover:border-cyan-300 hover:text-cyan-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 disabled:opacity-50" class="inline-flex items-center gap-2 rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-900 transition hover:border-cyan-300 hover:text-cyan-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 disabled:opacity-50 dark:border-white/20 dark:bg-white/10 dark:text-white dark:hover:text-cyan-200"
@click="$emit('prevPage')" @click="$emit('prevPage')"
:disabled="currentPage === 1" :disabled="currentPage === 1"
> >
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 19.5-7.5-7.5 7.5-7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="m15.75 19.5-7.5-7.5 7.5-7.5" />
</svg> </svg>
{{ __('navigation.previous') }} {{ __('api.navigation.previous') }}
</button> </button>
<button <button
type="button" type="button"
class="inline-flex items-center gap-2 rounded-full border border-emerald-300 bg-emerald-400/20 px-4 py-2 text-sm font-semibold text-emerald-100 transition hover:bg-emerald-400/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-300 disabled:opacity-50" class="inline-flex items-center gap-2 rounded-full border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-700 transition hover:bg-emerald-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-300 disabled:opacity-50 dark:bg-emerald-400/20 dark:text-emerald-100 dark:hover:bg-emerald-400/40"
@click="$emit('nextPage')" @click="$emit('nextPage')"
:disabled="currentPage === totalPages" :disabled="currentPage === totalPages"
> >
{{ __('navigation.next') }} {{ __('api.navigation.next') }}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg> </svg>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="fixed inset-0 z-[120] flex items-center justify-center bg-slate-950/70 px-6 py-10 backdrop-blur" @click.self="$emit('close')"> <div class="fixed inset-0 z-[120] flex items-center justify-center bg-slate-900/30 px-6 py-10 backdrop-blur dark:bg-slate-950/70" @click.self="$emit('close')">
<div class="w-full max-w-md rounded-[2.5rem] border border-white/10 bg-gradient-to-b from-slate-900 via-slate-900/90 to-slate-950 p-8 text-white shadow-[0_35px_80px_rgba(2,6,23,0.65)]"> <div class="w-full max-w-md rounded-[2.5rem] border border-slate-200 bg-gradient-to-b from-white via-white to-slate-50 p-8 text-slate-900 shadow-[0_35px_80px_rgba(2,6,23,0.15)] transition-colors dark:border-white/10 dark:from-slate-900 dark:via-slate-900/90 dark:to-slate-950 dark:text-white dark:shadow-[0_35px_80px_rgba(2,6,23,0.65)]">
<div class="flex items-start justify-between gap-4"> <div class="flex items-start justify-between gap-4">
<div> <div>
<p class="text-xs uppercase tracking-[0.5em] text-slate-400">{{ __('api.print_dialog.title') }}</p> <p class="text-xs uppercase tracking-[0.5em] text-slate-400">{{ __('api.print_dialog.title') }}</p>
@@ -12,7 +12,7 @@
</div> </div>
<button <button
type="button" type="button"
class="rounded-full border border-white/10 p-2 text-slate-200 transition hover:border-rose-300 hover:text-rose-300" class="rounded-full border border-slate-200 p-2 text-slate-500 transition hover:border-rose-300 hover:text-rose-400 dark:border-white/10 dark:text-slate-200"
@click="$emit('close')" @click="$emit('close')"
:aria-label="__('api.print_dialog.cancel_button')" :aria-label="__('api.print_dialog.cancel_button')"
> >
@@ -23,12 +23,12 @@
</div> </div>
<div class="mt-8 flex flex-col gap-6"> <div class="mt-8 flex flex-col gap-6">
<div class="rounded-3xl border border-white/10 bg-white/5 p-6 text-center text-slate-200"> <div class="rounded-3xl border border-slate-200 bg-white p-6 text-center text-slate-600 dark:border-white/10 dark:bg-white/5 dark:text-slate-200">
<p class="text-sm uppercase tracking-[0.4em] text-slate-400">Anzahl</p> <p class="text-sm uppercase tracking-[0.4em] text-slate-400">Anzahl</p>
<div class="mt-4 flex items-center justify-center gap-6"> <div class="mt-4 flex items-center justify-center gap-6">
<button <button
type="button" type="button"
class="flex h-14 w-14 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-3xl font-semibold text-white transition hover:border-emerald-300 hover:text-emerald-200" class="flex h-14 w-14 items-center justify-center rounded-2xl border border-slate-200 bg-white text-3xl font-semibold text-slate-900 transition hover:border-emerald-300 hover:text-emerald-600 dark:border-white/20 dark:bg-white/10 dark:text-white dark:hover:text-emerald-200"
@click="decrementQuantity" @click="decrementQuantity"
aria-label="-1" aria-label="-1"
> >
@@ -37,7 +37,7 @@
<div class="min-w-[4rem] text-6xl font-bold tracking-tight text-white">{{ quantity }}</div> <div class="min-w-[4rem] text-6xl font-bold tracking-tight text-white">{{ quantity }}</div>
<button <button
type="button" type="button"
class="flex h-14 w-14 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-3xl font-semibold text-white transition hover:border-emerald-300 hover:text-emerald-200" class="flex h-14 w-14 items-center justify-center rounded-2xl border border-slate-200 bg-white text-3xl font-semibold text-slate-900 transition hover:border-emerald-300 hover:text-emerald-600 dark:border-white/20 dark:bg-white/10 dark:text-white dark:hover:text-emerald-200"
@click="incrementQuantity" @click="incrementQuantity"
aria-label="+1" aria-label="+1"
> >
@@ -46,7 +46,7 @@
</div> </div>
</div> </div>
<p class="text-center text-xs uppercase tracking-[0.4em] text-slate-400"> <p class="text-center text-xs uppercase tracking-[0.4em] text-slate-500 dark:text-slate-400">
Du steuerst direkt den Sofortdruck. Du steuerst direkt den Sofortdruck.
</p> </p>
</div> </div>
@@ -54,14 +54,14 @@
<div class="mt-8 flex flex-wrap gap-3"> <div class="mt-8 flex flex-wrap gap-3">
<button <button
type="button" type="button"
class="flex-1 rounded-full border border-white/20 bg-white/5 px-5 py-3 text-center text-sm font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-rose-300 hover:text-rose-200" class="flex-1 rounded-full border border-slate-300 bg-white px-5 py-3 text-center text-sm font-semibold uppercase tracking-[0.2em] text-slate-600 transition hover:border-rose-300 hover:text-rose-400 dark:border-white/20 dark:bg-white/5 dark:text-slate-200 dark:hover:text-rose-200"
@click="$emit('close')" @click="$emit('close')"
> >
{{ __('api.print_dialog.cancel_button') }} {{ __('api.print_dialog.cancel_button') }}
</button> </button>
<button <button
type="button" type="button"
class="flex-1 rounded-full border border-emerald-300 bg-emerald-400/20 px-5 py-3 text-center text-sm font-semibold uppercase tracking-[0.2em] text-emerald-100 transition hover:bg-emerald-400/40" class="flex-1 rounded-full border border-emerald-400 bg-emerald-50 px-5 py-3 text-center text-sm font-semibold uppercase tracking-[0.2em] text-emerald-700 transition hover:bg-emerald-100 dark:bg-emerald-400/20 dark:text-emerald-100 dark:hover:bg-emerald-400/40"
@click="confirmPrint" @click="confirmPrint"
> >
{{ __('api.print_dialog.print_button') }} {{ __('api.print_dialog.print_button') }}

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="flex h-full flex-col gap-4 rounded-3xl border border-white/10 bg-white/5 p-4 text-white"> <div class="flex h-full flex-col gap-4 rounded-3xl border border-slate-200 bg-white p-4 text-slate-900 dark:border-white/10 dark:bg-white/5 dark:text-white">
<div class="flex items-center justify-between gap-2 text-xs uppercase tracking-[0.3em] text-slate-300"> <div class="flex items-center justify-between gap-2 text-xs uppercase tracking-[0.3em] text-slate-500 dark:text-slate-300">
<span>Varianten</span> <span>Varianten</span>
<button <button
type="button" type="button"
class="rounded-full border border-white/20 p-1 text-white/70 transition hover:border-rose-300 hover:text-rose-300" class="rounded-full border border-slate-200 p-1 text-slate-500 transition hover:border-rose-300 hover:text-rose-400 dark:border-white/20 dark:text-white/70 dark:hover:text-rose-300"
@click="$emit('close')" @click="$emit('close')"
aria-label="Auswahl schließen" aria-label="Auswahl schließen"
> >
@@ -15,10 +15,10 @@
</div> </div>
<div class="flex-1 space-y-3 overflow-y-auto pr-1"> <div class="flex-1 space-y-3 overflow-y-auto pr-1">
<p v-if="isLoading" class="rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-center text-sm text-slate-300"> <p v-if="isLoading" class="rounded-2xl border border-slate-200 bg-white px-4 py-3 text-center text-sm text-slate-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-300">
Stile werden geladen Stile werden geladen
</p> </p>
<p v-else-if="loadError" class="rounded-2xl border border-rose-400/40 bg-rose-500/10 px-4 py-3 text-center text-sm text-rose-100"> <p v-else-if="loadError" class="rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-center text-sm text-rose-500 dark:border-rose-400/40 dark:bg-rose-500/10 dark:text-rose-100">
{{ loadError }} {{ loadError }}
</p> </p>
<template v-else> <template v-else>
@@ -26,7 +26,7 @@
v-for="style in styles" v-for="style in styles"
:key="style.id" :key="style.id"
type="button" type="button"
class="group flex w-full items-center gap-4 rounded-2xl border border-white/10 bg-white/5 p-3 text-left transition hover:border-emerald-300" class="group flex w-full items-center gap-4 rounded-2xl border border-slate-200 bg-white/90 p-3 text-left text-slate-900 transition hover:border-emerald-300 hover:bg-emerald-50/50 dark:border-white/10 dark:bg-white/5 dark:text-white"
@click="selectStyle(style)" @click="selectStyle(style)"
> >
<img <img
@@ -38,13 +38,13 @@
/> />
<div <div
v-else v-else
class="flex h-24 w-24 flex-none items-center justify-center rounded-2xl border border-dashed border-white/20 text-[0.65rem] uppercase tracking-[0.4em] text-slate-400" class="flex h-24 w-24 flex-none items-center justify-center rounded-2xl border border-dashed border-slate-200 text-[0.65rem] uppercase tracking-[0.4em] text-slate-500 dark:border-white/20 dark:text-slate-400"
> >
AI AI
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<p class="text-base font-semibold text-white">{{ style.title }}</p> <p class="text-base font-semibold text-slate-900 dark:text-white">{{ style.title }}</p>
<p class="text-sm text-slate-200">{{ style.description }}</p> <p class="text-sm text-slate-500 dark:text-slate-200">{{ style.description }}</p>
</div> </div>
</button> </button>
</template> </template>

View File

@@ -1,20 +1,20 @@
<template> <template>
<div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"> <div class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white p-4 rounded-lg shadow-lg max-w-3xl w-full text-center"> <div class="bg-white p-4 rounded-lg shadow-lg max-w-3xl w-full text-center">
<h2 class="text-xl font-bold mb-4">{{ __('styled_image_display.title') }}</h2> <h2 class="text-xl font-bold mb-4">{{ __('api.styled_image_display.title') }}</h2>
<img :src="image.path" alt="Styled Image" class="max-w-full h-auto mx-auto mb-4 rounded-md" /> <img :src="image.path" alt="Styled Image" class="max-w-full h-auto mx-auto mb-4 rounded-md" />
<div class="flex justify-center space-x-4"> <div class="flex justify-center space-x-4">
<button <button
@click="$emit('keep', image)" @click="$emit('keep', image)"
class="px-6 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 focus:outline-hidden focus:ring-2 focus:ring-green-500 focus:ring-opacity-50" class="px-6 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 focus:outline-hidden focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"
> >
{{ __('styled_image_display.keep_button') }} {{ __('api.styled_image_display.keep_button') }}
</button> </button>
<button <button
@click="$emit('delete', image)" @click="$emit('delete', image)"
class="px-6 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-hidden focus:ring-2 focus:ring-red-500 focus:ring-opacity-50" class="px-6 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 focus:outline-hidden focus:ring-2 focus:ring-red-500 focus:ring-opacity-50"
> >
{{ __('styled_image_display.delete_button') }} {{ __('api.styled_image_display.delete_button') }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,20 +1,19 @@
<template> <template>
<Head title="Start" /> <Head title="Start" />
<div class="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-slate-100"> <div class="min-h-screen bg-gradient-to-br from-slate-100 via-white to-slate-200 text-slate-900 transition-colors duration-500 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 dark:text-slate-100">
<div class="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 py-8 sm:px-6 lg:px-8"> <div class="mx-auto flex w-full max-w-7xl flex-col gap-6 px-2 py-8 sm:px-4 lg:px-6">
<header class="rounded-3xl border border-white/10 bg-white/5 p-6 shadow-2xl backdrop-blur"> <header class="rounded-3xl border border-slate-200 bg-white/90 p-6 text-slate-900 shadow-2xl backdrop-blur transition-colors duration-300 dark:border-white/10 dark:bg-white/5 dark:text-white">
<div class="flex flex-wrap items-start justify-between gap-6"> <div class="flex flex-wrap items-start justify-between gap-6">
<div> <div>
<p class="text-xs uppercase tracking-[0.35em] text-slate-400">Live Gallery</p> <p class="text-xs uppercase tracking-[0.35em] text-slate-500 dark:text-slate-400">Live Gallery</p>
<h1 class="mt-2 text-3xl font-semibold text-white sm:text-4xl"> <h1 class="mt-1 text-3xl font-semibold text-slate-900 dark:text-white sm:text-4xl">
{{ props.galleryHeading }} {{ props.galleryHeading }}
</h1> </h1>
<p class="mt-1 text-sm text-slate-400">Touch-friendly wall with the freshest images.</p>
</div> </div>
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<button <button
type="button" type="button"
class="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-2 text-sm font-semibold text-white transition hover:border-emerald-400 hover:text-emerald-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400 disabled:opacity-60" class="inline-flex items-center gap-2 rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-900 shadow-sm transition hover:border-emerald-400 hover:text-emerald-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400 disabled:opacity-60 dark:border-white/20 dark:bg-white/10 dark:text-white dark:hover:text-emerald-200"
@click="handleManualRefresh" @click="handleManualRefresh"
:disabled="isRefreshing" :disabled="isRefreshing"
> >
@@ -36,7 +35,7 @@
</button> </button>
<button <button
type="button" type="button"
class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm font-semibold text-white transition hover:border-cyan-300 hover:text-cyan-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400" class="inline-flex items-center gap-2 rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-900 shadow-sm transition hover:border-cyan-400 hover:text-cyan-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 dark:border-white/10 dark:bg-white/5 dark:text-white dark:hover:text-cyan-200"
@click="toggleTheme" @click="toggleTheme"
> >
<svg v-if="currentTheme === 'light'" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <svg v-if="currentTheme === 'light'" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
@@ -53,9 +52,9 @@
</button> </button>
</div> </div>
</div> </div>
<div class="mt-4 flex flex-wrap items-center gap-4 text-xs text-slate-300"> <div class="mt-4 flex flex-wrap items-center gap-4 text-xs text-slate-500 dark:text-slate-300">
<div class="inline-flex items-center gap-2 rounded-full bg-emerald-400/10 px-3 py-1 text-emerald-200"> <div class="inline-flex items-center gap-2 rounded-full bg-emerald-500/10 px-3 py-1 text-emerald-700 dark:text-emerald-200">
<span class="h-2 w-2 rounded-full bg-emerald-400 animate-pulse"></span> <span class="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></span>
Auto-Refresh · alle {{ refreshIntervalSeconds }}s Auto-Refresh · alle {{ refreshIntervalSeconds }}s
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -72,12 +71,12 @@
</header> </header>
<section <section
class="rounded-3xl border border-white/5 bg-white/5 p-4 shadow-2xl backdrop-blur touch-pan-y" class="rounded-3xl border border-slate-200 bg-white/80 p-3 shadow-2xl backdrop-blur transition-colors duration-300 touch-pan-y dark:border-white/5 dark:bg-white/5 sm:p-4"
@touchstart.passive="handleTouchStart" @touchstart.passive="handleTouchStart"
@touchmove.passive="handleTouchMove" @touchmove.passive="handleTouchMove"
@touchend="handleTouchEnd" @touchend="handleTouchEnd"
> >
<GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" :translations="props.translations" /> <GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" />
</section> </section>
<Navigation :currentPage="currentPage" :totalPages="totalPages" @prevPage="prevPage" @nextPage="nextPage" /> <Navigation :currentPage="currentPage" :totalPages="totalPages" @prevPage="prevPage" @nextPage="nextPage" />
@@ -133,10 +132,6 @@ const props = defineProps({
type: String, type: String,
default: 'Gallery', default: 'Gallery',
}, },
translations: {
type: Object,
default: () => ({}),
},
images: { images: {
type: Array, type: Array,
default: () => [], default: () => [],

View File

@@ -16,6 +16,20 @@ library.add(faPrint, faMagicWandSparkles, faXmark);
const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
const resolveTranslation = (source, path) => {
if (!source || !path) {
return undefined;
}
return path.split('.').reduce((acc, segment) => {
if (acc && Object.prototype.hasOwnProperty.call(acc, segment)) {
return acc[segment];
}
return undefined;
}, source);
};
createInertiaApp({ createInertiaApp({
title: (title) => `${title} - ${appName}`, title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
@@ -27,18 +41,29 @@ createInertiaApp({
.component('font-awesome-icon', FontAwesomeIcon) // Register Font Awesome component .component('font-awesome-icon', FontAwesomeIcon) // Register Font Awesome component
.mixin({ .mixin({
methods: { methods: {
__: (key, replace = {}) => { __: function (key, replace = {}) {
let translation = props.initialPage.props.translations[key]; const sources = [
this?.$page?.props?.translations,
props.initialPage.props.translations,
];
if (translation === undefined) { let translation;
translation = key; // Fallback to key if translation not found
for (const source of sources) {
const value = resolveTranslation(source, key);
if (value !== undefined) {
translation = value;
break;
}
} }
for (let placeholder in replace) { let output = translation ?? key;
translation = translation.replace(`:${placeholder}`, replace[placeholder]);
}
return translation; Object.entries(replace).forEach(([placeholder, val]) => {
output = output.replace(`:${placeholder}`, val);
});
return output;
}, },
}, },
}) })

View File

@@ -8,23 +8,35 @@ return [
'image_deleted_successfully' => 'Bild erfolgreich gelöscht.', 'image_deleted_successfully' => 'Bild erfolgreich gelöscht.',
'image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden.', 'image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden.',
'no_styles_available' => 'Keine Stile oder API-Anbieter aktiviert/verfügbar.', 'no_styles_available' => 'Keine Stile oder API-Anbieter aktiviert/verfügbar.',
'api.dark_mode' => 'Dunkler Modus', 'dark_mode' => 'Dunkler Modus',
'api.light_mode' => 'Heller Modus', 'light_mode' => 'Heller Modus',
'gallery_title' => 'Eure Bilder aus der Fotobox', 'gallery_title' => 'Eure Bilder aus der Fotobox',
'navigation.previous' => 'Zurück', 'navigation' => [
'navigation.next' => 'Weiter', 'previous' => 'Zurück',
'navigation.page_of' => 'Seite :currentPage von :totalPages', 'next' => 'Weiter',
'loading_spinner.processing_image' => 'Bild wird verarbeitet...', 'page_of' => 'Seite :currentPage von :totalPages',
'styled_image_display.title' => 'Neu gestyltes Bild', ],
'styled_image_display.keep_button' => 'Behalten', 'loading_spinner' => [
'styled_image_display.delete_button' => 'Löschen', 'processing_image' => 'Bild wird verarbeitet...',
],
'styled_image_display' => [
'title' => 'Neu gestyltes Bild',
'keep_button' => 'Behalten',
'delete_button' => 'Löschen',
],
'print_command_sent_successfully' => 'Druckbefehl erfolgreich gesendet.', 'print_command_sent_successfully' => 'Druckbefehl erfolgreich gesendet.',
'failed_to_send_print_command' => 'Druckbefehl konnte nicht gesendet werden.', 'failed_to_send_print_command' => 'Druckbefehl konnte nicht gesendet werden.',
'api.print_dialog.title' => 'Bild drucken', 'print_dialog' => [
'api.print_dialog.quantity_prompt' => 'Wie viele Kopien möchten Sie drucken?', 'title' => 'Bild drucken',
'api.print_dialog.cancel_button' => 'Abbrechen', 'quantity_prompt' => 'Wie viele Kopien möchtest du drucken?',
'api.print_dialog.print_button' => 'Drucken', 'cancel_button' => 'Abbrechen',
'api.download_button' => 'Herunterladen', 'print_button' => 'Drucken',
'api.download_success' => 'Download gestartet!', ],
'api.download_error' => 'Download fehlgeschlagen.', 'download_button' => 'Herunterladen',
'download_success' => 'Download gestartet!',
'download_error' => 'Download fehlgeschlagen.',
'gallery' => [
'tap_to_open' => 'Zum Öffnen tippen',
'empty' => 'Noch keine Bilder vorhanden.',
],
]; ];

View File

@@ -11,20 +11,32 @@ return [
'dark_mode' => 'Dark Mode', 'dark_mode' => 'Dark Mode',
'light_mode' => 'Light Mode', 'light_mode' => 'Light Mode',
'gallery_title' => 'Your images from the photobooth', 'gallery_title' => 'Your images from the photobooth',
'navigation.previous' => 'Previous', 'navigation' => [
'navigation.next' => 'Next', 'previous' => 'Previous',
'navigation.page_of' => 'Page :currentPage of :totalPages', 'next' => 'Next',
'loading_spinner.processing_image' => 'Processing image...', 'page_of' => 'Page :currentPage of :totalPages',
'styled_image_display.title' => 'Newly Styled Image', ],
'styled_image_display.keep_button' => 'Keep', 'loading_spinner' => [
'styled_image_display.delete_button' => 'Delete', 'processing_image' => 'Processing image...',
],
'styled_image_display' => [
'title' => 'Newly Styled Image',
'keep_button' => 'Keep',
'delete_button' => 'Delete',
],
'print_command_sent_successfully' => 'Print command sent successfully.', 'print_command_sent_successfully' => 'Print command sent successfully.',
'failed_to_send_print_command' => 'Failed to send print command.', 'failed_to_send_print_command' => 'Failed to send print command.',
'print_dialog.title' => 'Print Image', 'print_dialog' => [
'print_dialog.quantity_prompt' => 'How many copies would you like to print?', 'title' => 'Print Image',
'print_dialog.cancel_button' => 'Cancel', 'quantity_prompt' => 'How many copies would you like to print?',
'print_dialog.print_button' => 'Print', 'cancel_button' => 'Cancel',
'print_button' => 'Print',
],
'download_button' => 'Download', 'download_button' => 'Download',
'download_success' => 'Download started!', 'download_success' => 'Download started!',
'download_error' => 'Download failed.', 'download_error' => 'Download failed.',
'gallery' => [
'tap_to_open' => 'Tap to open',
'empty' => 'No images available yet.',
],
]; ];