language files combined, settings fixed, "new" badge integrated
This commit is contained in:
@@ -1,3 +1,23 @@
|
||||
@import '@fortawesome/fontawesome-svg-core/styles.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Theme variables */
|
||||
:root {
|
||||
--color-background: #ffffff;
|
||||
--color-text: #000000;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--color-background: #333333; /* Dark grey */
|
||||
--color-text: #ffffff;
|
||||
}
|
||||
|
||||
/* Apply theme to body */
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
@@ -8,20 +8,31 @@
|
||||
@click="$emit('imageTapped', image, $event)"
|
||||
>
|
||||
<img :src="image.path" :alt="'Image ' + image.name" />
|
||||
<div v-if="image.is_new" class="new-badge">{{ __('new') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
images: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
translations: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['imageTapped']);
|
||||
|
||||
const __ = (key) => {
|
||||
return props.translations[key] || key;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -46,6 +57,7 @@ const emits = defineEmits(['imageTapped']);
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
position: relative; /* Added for badge positioning */
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
@@ -54,4 +66,17 @@ const emits = defineEmits(['imageTapped']);
|
||||
object-fit: cover; /* Bilder zuschneiden, um den Bereich zu füllen */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.new-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
@@ -4,16 +4,45 @@
|
||||
<div class="context-menu-image-preview">
|
||||
<img :src="image.path" :alt="'Selected Image ' + image.name" />
|
||||
</div>
|
||||
<ul>
|
||||
<li @click="$emit('print', image)">Drucken</li>
|
||||
<li @click="$emit('changeStyle', image)">Stil ändern</li>
|
||||
<li @click="$emit('close')">Schließen</li>
|
||||
</ul>
|
||||
<div class="context-menu-options">
|
||||
<ul v-if="!showStyleSelectorView">
|
||||
<li @click="$emit('print', image)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
||||
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.25a.75.75 0 0 0 1.5 0V3.375c0-.39.315-.75.75-.75h6.75c.39 0 .75.36.75.75v2.25a.75.75 0 0 0 1.5 0V3.375c0-1.036-.84-1.875-1.875-1.875H7.875Z" clip-rule="evenodd" />
|
||||
<path fill-rule="evenodd" d="M19.875 9a.75.75 0 0 0-.75-.75H4.875a.75.75 0 0 0-.75.75v6.75c0 1.036.84 1.875 1.875 1.875h11.25c1.036 0 1.875-.84 1.875-1.875V9ZM12 12.75a.75.75 0 0 0 0 1.5h.007a.75.75 0 0 0 0-1.5H12Z" clip-rule="evenodd" />
|
||||
<path d="M2.25 17.75a.75.75 0 0 0 0 1.5h19.5a.75.75 0 0 0 0-1.5H2.25Z" />
|
||||
</svg>
|
||||
Drucken
|
||||
</li>
|
||||
<li @click="showStyleSelectorView = true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
||||
<path fill-rule="evenodd" d="M10.788 3.212a.75.75 0 0 0-1.06 0L7.212 5.788a.75.75 0 0 0 0 1.06l1.59 1.59a.75.75 0 0 0 1.06 0l2.576-2.576a.75.75 0 0 0 0-1.06L10.788 3.212ZM15.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 12a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM18.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 21a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Stil ändern
|
||||
</li>
|
||||
<li @click="$emit('close')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
||||
<path fill-rule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Schließen
|
||||
</li>
|
||||
</ul>
|
||||
<StyleSelector
|
||||
v-else
|
||||
:image_id="image.image_id"
|
||||
@styleSelected="(style, imageId) => $emit('styleSelected', style, imageId)"
|
||||
@back="showStyleSelectorView = false"
|
||||
@close="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import StyleSelector from './StyleSelector.vue';
|
||||
|
||||
const props = defineProps({
|
||||
position: {
|
||||
type: Object,
|
||||
@@ -25,7 +54,9 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['close', 'print', 'changeStyle']);
|
||||
const emits = defineEmits(['close', 'print', 'changeStyle', 'styleSelected']);
|
||||
|
||||
const showStyleSelectorView = ref(false);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -43,19 +74,19 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
||||
background: var(--color-background);
|
||||
border: 1px solid var(--color-text);
|
||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: row; /* Bild und Menü nebeneinander */
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
max-width: 90%;
|
||||
height: 80vh; /* 80% of viewport height */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.context-menu-image-preview {
|
||||
flex: 1; /* Nimmt den verfügbaren Platz ein */
|
||||
flex: 7; /* Takes 70% of the space (7 out of 10 parts) */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -65,14 +96,21 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
|
||||
.context-menu-image-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 80%; /* 80% width of its container */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.context-menu-options {
|
||||
flex: 3; /* Takes 30% of the space (3 out of 10 parts) */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.context-menu ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-left: 1px solid #eee; /* Trennlinie zwischen Bild und Menü */
|
||||
border-left: 1px solid var(--color-text); /* Trennlinie zwischen Bild und Menü */
|
||||
min-width: 150px; /* Mindestbreite für das Menü */
|
||||
}
|
||||
|
||||
@@ -80,7 +118,7 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
font-size: 1.1em;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px solid var(--color-text);
|
||||
}
|
||||
|
||||
.context-menu li:last-child {
|
||||
@@ -88,6 +126,7 @@ const emits = defineEmits(['close', 'print', 'changeStyle']);
|
||||
}
|
||||
|
||||
.context-menu li:hover {
|
||||
background: #f0f0f0;
|
||||
background: var(--color-text); /* Adjust hover background for dark mode */
|
||||
color: var(--color-background); /* Adjust hover text color for dark mode */
|
||||
}
|
||||
</style>
|
||||
@@ -27,9 +27,10 @@ const emits = defineEmits(['prevPage', 'nextPage']);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
background-color: var(--color-background);
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.navigation button {
|
||||
@@ -51,6 +52,6 @@ const emits = defineEmits(['prevPage', 'nextPage']);
|
||||
.navigation span {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: var(--color-text);
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<div class="context-menu-overlay" @click.self="$emit('close')">
|
||||
<div class="style-selector" :style="{ top: `${position.y}px`, left: `${position.x}px` }">
|
||||
<div class="style-selector-header">
|
||||
<span class="back-arrow" @click="$emit('back')">←</span>
|
||||
<h3>Verfügbare Stile</h3>
|
||||
</div>
|
||||
<div class="styles-list">
|
||||
<div
|
||||
v-for="style in styles"
|
||||
:key="style.id"
|
||||
class="style-item"
|
||||
@click="selectStyle(style)"
|
||||
>
|
||||
<img :src="'/storage/' + style.preview_image" :alt="style.title" class="style-thumbnail" />
|
||||
<div class="style-details">
|
||||
<h4>{{ style.title }}</h4>
|
||||
<p>{{ style.description }}</p>
|
||||
</div>
|
||||
<div class="style-selector">
|
||||
<div class="style-selector-header">
|
||||
<span class="back-arrow" @click="$emit('back')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M11.03 4.272a.75.75 0 0 1 0 1.06L6.31 10.5H20.25a.75.75 0 0 1 0 1.5H6.31l4.72 5.168a.75.75 0 0 1-1.06 1.06l-6-6a.75.75 0 0 1 0-1.06l6-6a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<h3>Verfügbare Stile</h3>
|
||||
</div>
|
||||
<div class="styles-list">
|
||||
<div
|
||||
v-for="style in styles"
|
||||
:key="style.id"
|
||||
class="style-item"
|
||||
@click="selectStyle(style)"
|
||||
>
|
||||
<img :src="'/storage/' + style.preview_image" :alt="style.title" class="style-thumbnail" />
|
||||
<div class="style-details">
|
||||
<h4>{{ style.title }}</h4>
|
||||
<p>{{ style.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,10 +32,6 @@ import { ref, onMounted } from 'vue';
|
||||
const styles = ref([]);
|
||||
|
||||
const props = defineProps({
|
||||
position: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
image_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
@@ -63,19 +61,6 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.context-menu-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5); /* Halbdurchsichtiger Hintergrund */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.style-selector {
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
@@ -83,10 +68,9 @@ onMounted(() => {
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
max-width: 100%; /* Adjusted to fit parent */
|
||||
max-height: 100%; /* Adjusted to fit parent */
|
||||
overflow: hidden;
|
||||
position: absolute; /* Positionierung innerhalb des Overlays */
|
||||
}
|
||||
|
||||
.style-selector-header {
|
||||
@@ -106,14 +90,16 @@ onMounted(() => {
|
||||
|
||||
.back-arrow {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
font-size: 1.5em;
|
||||
left: 5px; /* Adjusted position */
|
||||
font-size: 2em; /* Slightly larger */
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
padding: 10px; /* Larger clickable area */
|
||||
color: var(--color-text); /* Adapt to theme */
|
||||
}
|
||||
|
||||
.back-arrow:hover {
|
||||
color: #007bff;
|
||||
color: var(--color-background); /* Adjust hover color for dark mode */
|
||||
background: var(--color-text); /* Adjust hover background for dark mode */
|
||||
}
|
||||
|
||||
.styles-list {
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
<div class="home">
|
||||
<div class="main-content">
|
||||
<div class="gallery-container" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
|
||||
<h1 class="text-2xl font-bold text-center my-4">Style Gallery</h1>
|
||||
<GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" />
|
||||
<h1 class="text-2xl font-bold text-center my-4">{{ props.galleryHeading }}</h1>
|
||||
<div class="absolute top-4 right-4">
|
||||
<button @click="toggleTheme" class="theme-toggle-button">
|
||||
{{ currentTheme === 'light' ? __('api.dark_mode') : __('api.light_mode') }}
|
||||
</button>
|
||||
</div>
|
||||
<GalleryGrid :images="paginatedImages" @imageTapped="showContextMenu" :translations="props.translations" />
|
||||
<Navigation
|
||||
:currentPage="currentPage"
|
||||
:totalPages="totalPages"
|
||||
@@ -17,18 +22,10 @@
|
||||
v-if="currentOverlayComponent === 'contextMenu'"
|
||||
:position="contextMenuPosition"
|
||||
:image="selectedImage"
|
||||
@close="currentOverlayComponent = null"
|
||||
@close="currentOverlayComponent = null; selectedImage = null"
|
||||
@print="printImage"
|
||||
@changeStyle="showStyleSelector"
|
||||
/>
|
||||
|
||||
<StyleSelector
|
||||
v-if="currentOverlayComponent === 'styleSelector'"
|
||||
:position="contextMenuPosition"
|
||||
:image_id="selectedImage.image_id"
|
||||
@styleSelected="applyStyle"
|
||||
@back="goBackToContextMenu"
|
||||
@close="currentOverlayComponent = null"
|
||||
/>
|
||||
|
||||
<div v-if="errorMessage" class="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50">
|
||||
@@ -46,38 +43,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Navigation from '../Components/Navigation.vue';
|
||||
import GalleryGrid from '../Components/GalleryGrid.vue';
|
||||
import ImageContextMenu from '../Components/ImageContextMenu.vue';
|
||||
import StyleSelector from '../Components/StyleSelector.vue';
|
||||
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
|
||||
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
|
||||
import axios from 'axios';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
galleryHeading: String,
|
||||
translations: Object,
|
||||
});
|
||||
|
||||
const images = ref([]);
|
||||
const imagesPerPage = 12;
|
||||
const currentPage = ref(1);
|
||||
const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelector', 'styledImageDisplay'
|
||||
const contextMenuPosition = ref({ x: 0, y: 0 });
|
||||
const selectedImage = ref(null);
|
||||
const styledImage = ref(null); // To store the newly styled image
|
||||
const errorMessage = ref(null); // New ref for error messages
|
||||
const isLoading = ref(false); // New ref for loading state
|
||||
let fetchInterval = null;
|
||||
let touchStartX = 0;
|
||||
let touchEndX = 0;
|
||||
|
||||
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(images.value.length / imagesPerPage);
|
||||
});
|
||||
|
||||
const paginatedImages = computed(() => {
|
||||
const start = (currentPage.value - 1) * imagesPerPage;
|
||||
const end = start + imagesPerPage;
|
||||
return images.value.slice(start, end);
|
||||
});
|
||||
|
||||
const fetchImages = () => {
|
||||
axios.get('/api/images')
|
||||
@@ -90,6 +64,50 @@ const fetchImages = () => {
|
||||
});
|
||||
};
|
||||
|
||||
import Navigation from '../Components/Navigation.vue';
|
||||
import GalleryGrid from '../Components/GalleryGrid.vue';
|
||||
import ImageContextMenu from '../Components/ImageContextMenu.vue';
|
||||
import StyleSelector from '../Components/StyleSelector.vue';
|
||||
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
|
||||
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
|
||||
import axios from 'axios';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
const imagesPerPage = 12;
|
||||
const currentPage = ref(1);
|
||||
const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelector', 'styledImageDisplay'
|
||||
const contextMenuPosition = ref({ x: 0, y: 0 });
|
||||
const selectedImage = ref(null);
|
||||
const styledImage = ref(null); // To store the newly styled image
|
||||
const errorMessage = ref(null); // New ref for error messages
|
||||
const isLoading = ref(false); // New ref for loading state
|
||||
const currentTheme = ref('light'); // New ref for current theme
|
||||
|
||||
let touchStartX = 0;
|
||||
let touchEndX = 0;
|
||||
|
||||
const applyTheme = (theme) => {
|
||||
document.documentElement.classList.remove('light', 'dark');
|
||||
document.documentElement.classList.add(theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
currentTheme.value = theme;
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = currentTheme.value === 'light' ? 'dark' : 'light';
|
||||
applyTheme(newTheme);
|
||||
};
|
||||
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(images.value.length / imagesPerPage);
|
||||
});
|
||||
|
||||
const paginatedImages = computed(() => {
|
||||
const start = (currentPage.value - 1) * imagesPerPage;
|
||||
const end = start + imagesPerPage;
|
||||
return images.value.slice(start, end);
|
||||
});
|
||||
|
||||
const showError = (message) => {
|
||||
errorMessage.value = message;
|
||||
setTimeout(() => {
|
||||
@@ -142,8 +160,6 @@ const applyStyle = (style, imageId) => {
|
||||
const keepStyledImage = (imageToKeep) => {
|
||||
console.log('Keeping styled image:', imageToKeep);
|
||||
// Implement API call to mark image as kept/permanent if needed
|
||||
// For now, just refresh the image list to show the new image
|
||||
fetchImages();
|
||||
currentOverlayComponent.value = null; // Close the display
|
||||
};
|
||||
|
||||
@@ -186,6 +202,16 @@ const handleSwipeGesture = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Apply theme from localStorage on mount
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
applyTheme(savedTheme);
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
applyTheme('dark');
|
||||
} else {
|
||||
applyTheme('light');
|
||||
}
|
||||
|
||||
fetchImages();
|
||||
fetchInterval = setInterval(fetchImages, 5000);
|
||||
});
|
||||
@@ -216,4 +242,35 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.theme-toggle-button {
|
||||
margin-top: 10px;
|
||||
padding: 8px 15px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.theme-toggle-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.image-visibility-toggle-button {
|
||||
margin-top: 10px;
|
||||
padding: 8px 15px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.image-visibility-toggle-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
</style>
|
||||
@@ -6,15 +6,25 @@ import { createInertiaApp } from '@inertiajs/vue3';
|
||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
|
||||
|
||||
/* Font Awesome imports */
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { faPrint, faMagicWandSparkles, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
// Add icons to the library
|
||||
library.add(faPrint, faMagicWandSparkles, faXmark);
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||
|
||||
createInertiaApp({
|
||||
title: (title) => `${title} - ${appName}`,
|
||||
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
|
||||
setup({ el, App, props, plugin }) {
|
||||
console.log('Inertia Page Props (app.js):', props.initialPage.props);
|
||||
return createApp({ render: () => h(App, props) })
|
||||
.use(plugin)
|
||||
.use(ZiggyVue)
|
||||
.component('font-awesome-icon', FontAwesomeIcon) // Register Font Awesome component
|
||||
.mixin({
|
||||
methods: {
|
||||
__: (key, replace = {}) => {
|
||||
|
||||
83
resources/js/custom/navigation-state.js
Normal file
83
resources/js/custom/navigation-state.js
Normal file
@@ -0,0 +1,83 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const navigation = document.querySelector('.fi-main-nav');
|
||||
if (!navigation) return;
|
||||
|
||||
const KEY = 'navigation_state';
|
||||
const TABLE_PAGES_KEY = 'table_pages_state';
|
||||
|
||||
// Function to get the state from session via a custom endpoint
|
||||
const getState = () => {
|
||||
return JSON.parse(sessionStorage.getItem(KEY)) || {};
|
||||
};
|
||||
|
||||
// Function to save the state to session via a custom endpoint
|
||||
const saveState = (state) => {
|
||||
sessionStorage.setItem(KEY, JSON.stringify(state));
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
fetch('/api/admin/navigation-state', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
},
|
||||
body: JSON.stringify({ groups: state }),
|
||||
});
|
||||
};
|
||||
|
||||
// Function to get table page state
|
||||
const getTablePageState = () => {
|
||||
return JSON.parse(sessionStorage.getItem(TABLE_PAGES_KEY)) || {};
|
||||
};
|
||||
|
||||
// Function to save table page state
|
||||
const saveTablePageState = (tableId, page) => {
|
||||
let tablePages = getTablePageState();
|
||||
tablePages[tableId] = page;
|
||||
sessionStorage.setItem(TABLE_PAGES_KEY, JSON.stringify(tablePages));
|
||||
};
|
||||
|
||||
let currentState = getState();
|
||||
let currentTablePages = getTablePageState();
|
||||
|
||||
// Apply the saved navigation state on page load
|
||||
const groups = navigation.querySelectorAll('.fi-nav-group');
|
||||
groups.forEach(group => {
|
||||
const label = group.querySelector('.fi-nav-group-label').textContent.trim();
|
||||
if (currentState[label] === 'collapsed') {
|
||||
group.classList.add('collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listeners to save navigation state on click
|
||||
navigation.addEventListener('click', (e) => {
|
||||
const labelElement = e.target.closest('.fi-nav-group-label');
|
||||
if (!labelElement) return;
|
||||
|
||||
const group = labelElement.closest('.fi-nav-group');
|
||||
const label = labelElement.textContent.trim();
|
||||
|
||||
if (group.classList.contains('collapsed')) {
|
||||
currentState[label] = 'expanded';
|
||||
group.classList.remove('collapsed');
|
||||
} else {
|
||||
currentState[label] = 'collapsed';
|
||||
group.classList.add('collapsed');
|
||||
}
|
||||
|
||||
saveState(currentState);
|
||||
});
|
||||
|
||||
// Livewire hook to save table page when pagination changes
|
||||
Livewire.hook('component.initialized', (component) => {
|
||||
if (component.name.includes('table')) {
|
||||
const tableId = component.el.id;
|
||||
if (currentTablePages[tableId]) {
|
||||
component.set('tablePage', currentTablePages[tableId], false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Livewire.on('table-pagination-updated', (tableId, page) => {
|
||||
saveTablePageState(tableId, page);
|
||||
});
|
||||
});
|
||||
@@ -8,4 +8,10 @@ return [
|
||||
'image_deleted_successfully' => 'Bild erfolgreich gelöscht.',
|
||||
'image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden.',
|
||||
'no_styles_available' => 'Keine Stile oder API-Anbieter aktiviert/verfügbar.',
|
||||
'api.dark_mode' => 'Dunkler Modus',
|
||||
'api.light_mode' => 'Heller Modus',
|
||||
'gallery_title' => 'Eure Bilder aus der Fotobox',
|
||||
'navigation.previous' => 'Zurück',
|
||||
'navigation.next' => 'Weiter',
|
||||
'navigation.page_of' => 'Seite :currentPage von :totalPages',
|
||||
];
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'gallery_title' => 'Eure Bilder aus der Fotobox',
|
||||
'api.image_uploaded_successfully' => 'Bild erfolgreich hochgeladen',
|
||||
'api.style_or_provider_not_found' => 'Stil oder API-Anbieter nicht gefunden',
|
||||
'api.image_or_provider_not_found' => 'Bild oder API-Anbieter nicht gefunden',
|
||||
'navigation.previous' => 'Zurück',
|
||||
'navigation.next' => 'Weiter',
|
||||
'navigation.page_of' => 'Seite :currentPage von :totalPages',
|
||||
];
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'gallery_heading' => 'Galerie Überschrift',
|
||||
|
||||
'saved_successfully' => 'Einstellungen erfolgreich gespeichert.',
|
||||
'save_button' => 'Speichern',
|
||||
'new' => 'Neu',
|
||||
];
|
||||
|
||||
@@ -8,4 +8,10 @@ return [
|
||||
'image_deleted_successfully' => 'Image deleted successfully.',
|
||||
'image_or_provider_not_found' => 'Image or API provider not found.',
|
||||
'no_styles_available' => 'No styles or API providers enabled/available.',
|
||||
'dark_mode' => 'Dark Mode',
|
||||
'light_mode' => 'Light Mode',
|
||||
'gallery_title' => 'Your images from the photobooth',
|
||||
'navigation.previous' => 'Previous',
|
||||
'navigation.next' => 'Next',
|
||||
'navigation.page_of' => 'Page :currentPage of :totalPages',
|
||||
];
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'gallery_title' => 'Your images from the photobooth',
|
||||
'api.image_uploaded_successfully' => 'Image uploaded successfully',
|
||||
'api.style_or_provider_not_found' => 'Style or API Provider not found',
|
||||
'api.image_or_provider_not_found' => 'Image or API Provider not found',
|
||||
'navigation.previous' => 'Previous',
|
||||
'navigation.next' => 'Next',
|
||||
'navigation.page_of' => 'Page :currentPage of :totalPages',
|
||||
];
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'gallery_heading' => 'Gallery Heading',
|
||||
|
||||
'saved_successfully' => 'Settings saved successfully.',
|
||||
'save_button' => 'Save',
|
||||
'new' => 'New',
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user