finished the upgrade to filament 4. completely revamped the frontend with codex, now it looks great!
This commit is contained in:
@@ -50,9 +50,8 @@
|
||||
<p class="text-sm font-normal text-slate-500 dark:text-slate-400">Lass die KI dein Motiv verzaubern.</p>
|
||||
</div>
|
||||
<span class="flex h-12 w-12 items-center justify-center rounded-full bg-white/60 text-slate-900 shadow-md dark:bg-slate-800/70 dark:text-white">
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904a2.25 2.25 0 0 1 0-3.808l.99-.572a1.125 1.125 0 0 0 0-1.948l-.99-.572a2.25 2.25 0 0 1 0-3.808l5.25-3.038A2.25 2.25 0 0 1 17.625 3v18a2.25 2.25 0 0 1-3.563 1.844l-4.249-2.457Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.25a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-1.5 0v-12a.75.75 0 0 1 .75-.75Zm13.5 0a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-1.5 0v-12a.75.75 0 0 1 .75-.75Z" />
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v3m0 12v3m9-9h-3M6 12H3m13.5-6 1.5 1.5M7.5 18 6 19.5M18 7.5 19.5 6M6 4.5 4.5 6m9.75 3.75-2.122 2.122a2.25 2.25 0 0 0 0 3.182l2.122 2.121a2.25 2.25 0 0 0 3.182 0l2.121-2.121a2.25 2.25 0 0 0 0-3.182l-2.121-2.122a2.25 2.25 0 0 0-3.182 0Z" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref="stylesContainer" 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">
|
||||
Stile werden geladen …
|
||||
</p>
|
||||
@@ -31,7 +31,8 @@
|
||||
>
|
||||
<img
|
||||
v-if="style.preview_image"
|
||||
:data-src="'/storage/' + style.preview_image"
|
||||
:src="buildPreviewPath(style.preview_image)"
|
||||
loading="lazy"
|
||||
class="h-24 w-24 flex-none rounded-2xl object-cover opacity-90 transition group-hover:opacity-100"
|
||||
alt="Style preview"
|
||||
/>
|
||||
@@ -53,13 +54,11 @@
|
||||
|
||||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const styles = ref([]);
|
||||
const isLoading = ref(true);
|
||||
const loadError = ref(null);
|
||||
const stylesContainer = ref(null);
|
||||
let observer = null;
|
||||
|
||||
const props = defineProps({
|
||||
image_id: {
|
||||
@@ -70,31 +69,6 @@ const props = defineProps({
|
||||
|
||||
const emits = defineEmits(['styleSelected', 'close']);
|
||||
|
||||
const hydrateObserver = () => {
|
||||
if (!stylesContainer.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
observer = new IntersectionObserver(
|
||||
(entries, obs) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src;
|
||||
obs.unobserve(img);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ root: stylesContainer.value }
|
||||
);
|
||||
|
||||
stylesContainer.value.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));
|
||||
};
|
||||
|
||||
const fetchStyles = () => {
|
||||
isLoading.value = true;
|
||||
loadError.value = null;
|
||||
@@ -102,7 +76,6 @@ const fetchStyles = () => {
|
||||
.get('/api/styles')
|
||||
.then((response) => {
|
||||
styles.value = response.data;
|
||||
nextTick(hydrateObserver);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching styles:', error);
|
||||
@@ -117,13 +90,9 @@ const selectStyle = (style) => {
|
||||
emits('styleSelected', style, props.image_id);
|
||||
};
|
||||
|
||||
const buildPreviewPath = (preview) => `/storage/${preview}`;
|
||||
|
||||
onMounted(() => {
|
||||
fetchStyles();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -163,6 +163,24 @@ let refreshTimer = null;
|
||||
|
||||
const getImageIdentifier = (image) => image?.id ?? image?.image_id ?? null;
|
||||
|
||||
const extractFilenameFromHeader = (disposition) => {
|
||||
if (!disposition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filenameMatch = disposition.match(/filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i);
|
||||
if (!filenameMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const encodedName = filenameMatch[1] || filenameMatch[2];
|
||||
try {
|
||||
return decodeURIComponent(encodedName);
|
||||
} catch (error) {
|
||||
return encodedName;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.images,
|
||||
(newImages) => {
|
||||
@@ -306,8 +324,12 @@ const downloadImage = (imageParam = null) => {
|
||||
const blob = new Blob([response.data]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
const disposition = response.headers?.['content-disposition'];
|
||||
const filenameFromHeader = extractFilenameFromHeader(disposition);
|
||||
const fallbackName = image.name || `stylegallery_${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
||||
|
||||
link.href = url;
|
||||
link.download = image.name || 'image';
|
||||
link.download = filenameFromHeader || fallbackName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
Reference in New Issue
Block a user