generated from JustScreaMy/ProjectTemplate
fix gallery and menu
This commit is contained in:
parent
46be762b8a
commit
e641ade1b2
11 changed files with 275 additions and 136 deletions
|
@ -35,11 +35,14 @@ class ArticleListSerializer(serializers.ModelSerializer):
|
|||
def get_date(obj):
|
||||
return obj.date.strftime("%-d. %-m. %Y")
|
||||
|
||||
@staticmethod
|
||||
def get_image(obj):
|
||||
def get_image(self, obj):
|
||||
main_image = obj.images.order_by("main").first()
|
||||
url = 'http://localhost:8000'
|
||||
return f"{url}{main_image.image.url}" if main_image else None
|
||||
if main_image:
|
||||
return ArticleImageSerializer(main_image, context=self.context).data
|
||||
return {
|
||||
"image": "http://localhost:8000/media/images/image.jpg",
|
||||
"title": "Výchozí obrázek",
|
||||
}
|
||||
|
||||
|
||||
class EventListSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -12,6 +12,7 @@ h1 {
|
|||
margin-bottom: 1rem;
|
||||
margin-top: 2rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -20,6 +21,7 @@ h2 {
|
|||
text-align: center;
|
||||
word-break: break-word;
|
||||
margin-bottom: 2rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
@ -27,6 +29,7 @@ h3 {
|
|||
color: #666;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
h4 {
|
||||
|
@ -34,12 +37,14 @@ h4 {
|
|||
color: #666;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
color: #aaa;
|
||||
margin-top: 1rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
@ -68,6 +73,7 @@ h5 {
|
|||
.app__title {
|
||||
font-size: 2rem;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.app__tab {
|
||||
|
@ -95,6 +101,7 @@ h5 {
|
|||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contact__dialog__title {
|
||||
|
@ -102,6 +109,8 @@ h5 {
|
|||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: bold;
|
||||
word-break: break-word !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contact__button {
|
||||
|
@ -116,7 +125,6 @@ h5 {
|
|||
}
|
||||
|
||||
.contact {
|
||||
min-width: unset;
|
||||
margin: 10px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
@ -130,6 +138,7 @@ h5 {
|
|||
.contact__dialog__title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +147,7 @@ h5 {
|
|||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.article {
|
||||
|
@ -146,17 +156,20 @@ h5 {
|
|||
border-radius: 0;
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||
margin-bottom: 1rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.article__title {
|
||||
font-size: 1.4rem;
|
||||
color: #CF3476;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.article__date {
|
||||
font-size: 0.8rem;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.article__text {
|
||||
|
@ -165,6 +178,11 @@ h5 {
|
|||
line-height: 1.5;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.article__image:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.article__sign {
|
||||
|
@ -172,6 +190,7 @@ h5 {
|
|||
font-size: 1rem;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.show_more {
|
||||
|
@ -222,6 +241,7 @@ h5 {
|
|||
|
||||
.pricing-title {
|
||||
min-height: 3rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pricing-desc {
|
||||
|
@ -274,6 +294,7 @@ h5 {
|
|||
height: 100%;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.trainer-avatar {
|
||||
|
@ -310,6 +331,7 @@ h5 {
|
|||
margin-top: 1.2rem;
|
||||
margin-left: calc(20% - 2.5rem);
|
||||
margin-right: calc(20% - 2.5rem);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.advantage__title {
|
||||
|
@ -318,12 +340,14 @@ h5 {
|
|||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-top: 0.1rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.advantage__text {
|
||||
padding-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
@ -352,6 +376,7 @@ h5 {
|
|||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.about__parallax {
|
||||
|
@ -377,6 +402,8 @@ h5 {
|
|||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
display: block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.about__subtitle {
|
||||
|
@ -385,6 +412,8 @@ h5 {
|
|||
margin-bottom: 1rem;
|
||||
word-break: break-word;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.about__text {
|
||||
|
@ -392,6 +421,7 @@ h5 {
|
|||
font-size: 1.125rem;
|
||||
padding: 0 1rem;
|
||||
line-height: 1.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
@ -418,6 +448,23 @@ h5 {
|
|||
}
|
||||
}
|
||||
|
||||
.masonry-gallery {
|
||||
columns: 3;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.masonry-gallery {
|
||||
columns: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.masonry-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: black;
|
||||
cursor: default;
|
||||
}
|
|
@ -4,8 +4,8 @@
|
|||
src="public/dark-dance.jpg"
|
||||
scale="0.8"
|
||||
>
|
||||
<v-container id="about">
|
||||
<v-card class="about">
|
||||
<v-container>
|
||||
<v-card class="about" id="about">
|
||||
<v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
|
||||
<v-card-subtitle class="about__subtitle">Objevte kouzlo tance s námi!</v-card-subtitle>
|
||||
<v-card-text class="about__text">
|
||||
|
|
|
@ -38,11 +38,10 @@
|
|||
transition="dialog-bottom-transition"
|
||||
>
|
||||
<v-card class="contact pa-4">
|
||||
<v-card-title class="contact__dialog__title d-flex align-center">
|
||||
<h1 class="contact__dialog__title d-flex align-center">
|
||||
<span class="flex-grow-1 text-center" style="margin-left: 40px;">{{ chosenCourse.name }}</span>
|
||||
<v-btn icon="mdi-close" variant="text" @click="openDialog = false"></v-btn>
|
||||
</v-card-title>
|
||||
|
||||
</h1>
|
||||
<v-card-text v-if="chosenCourse.desc" class="text-center">
|
||||
<div class="pricing__subtitle text-body-1">{{ chosenCourse.desc }}</div>
|
||||
</v-card-text>
|
||||
|
@ -56,7 +55,7 @@
|
|||
<dialog-contact-form class="mt-2" />
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- <v-btn to="/kurzy" class="show_more">-->
|
||||
<!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
|
||||
|
|
|
@ -13,48 +13,48 @@
|
|||
<v-row>
|
||||
<v-col>
|
||||
<v-img
|
||||
:lazy-src="article.image"
|
||||
:src="article.image"
|
||||
aspect-ratio="1"
|
||||
class="article__image"
|
||||
@click="showCarousel = true"
|
||||
:src="article.image.image"
|
||||
:lazy-src="article.image.image"
|
||||
class="article__image rounded-lg"
|
||||
@click="openCarousel(article.image, article.images)"
|
||||
>
|
||||
<template v-slot:placeholder>
|
||||
<v-row justify="center" align="center" class="fill-height">
|
||||
<v-progress-circular color="grey-lighten-5" indeterminate></v-progress-circular>
|
||||
<v-progress-circular color="grey-lighten-5" indeterminate />
|
||||
</v-row>
|
||||
</template>
|
||||
<v-dialog v-model="showCarousel">
|
||||
<dialog-carousel
|
||||
:image="article.image"
|
||||
:images="article.images"
|
||||
:title="article.title"
|
||||
@isActive="showCarousel = false"
|
||||
/>
|
||||
</v-dialog>
|
||||
</v-img>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
|
||||
<v-col cols="12" md="8">
|
||||
<v-row class="article__text">{{ article.content }}</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="article__sign">Dne {{ article.date}}, {{ article.author }}</v-row>
|
||||
<v-row class="article__sign">Dne {{ article.date }}, {{ article.author }}</v-row>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-dialog v-model="showCarousel" max-width="90vw" v-if="selectedImages.length > 0">
|
||||
<dialog-carousel
|
||||
v-if="selectedImage"
|
||||
:image="selectedImage"
|
||||
:images="selectedImages"
|
||||
@isActive="showCarousel = false"
|
||||
/>
|
||||
</v-dialog>
|
||||
|
||||
<v-btn to="/aktuality" class="show_more" v-if="!props.forAll">
|
||||
<v-icon icon="mdi-chevron-down"/>Další aktuality<v-icon icon="mdi-chevron-down"/>
|
||||
<v-icon icon="mdi-chevron-down" />Další aktuality<v-icon icon="mdi-chevron-down" />
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import './assets/css/main.css'
|
||||
import { useAPI } from "~/composables/useAPI";
|
||||
|
||||
const showCarousel = ref(false);
|
||||
const selectedImage = ref<ArticleImage | null>(null);
|
||||
const selectedImages = ref<ArticleImage[]>([]);
|
||||
|
||||
const props = defineProps<{
|
||||
forAll: boolean
|
||||
|
@ -62,12 +62,13 @@ const props = defineProps<{
|
|||
|
||||
interface ArticleImage {
|
||||
image: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Article {
|
||||
id: number;
|
||||
title: string;
|
||||
image: string;
|
||||
image: ArticleImage;
|
||||
images: ArticleImage[];
|
||||
date: string;
|
||||
content: string;
|
||||
|
@ -76,15 +77,18 @@ interface Article {
|
|||
|
||||
const articles = ref<Article[]>([]);
|
||||
|
||||
const endpoint = props.forAll ? 'load-all-articles/' : 'load-articles/';
|
||||
|
||||
const { error, data } = await useAPI<Article[]>(endpoint, {method: "GET"});
|
||||
|
||||
if ( data.value ){
|
||||
articles.value = data.value;
|
||||
function openCarousel(image: ArticleImage, images: ArticleImage[]) {
|
||||
selectedImage.value = image;
|
||||
selectedImages.value = images;
|
||||
showCarousel.value = true;
|
||||
}
|
||||
else if (error.value) {
|
||||
|
||||
const endpoint = props.forAll ? 'load-all-articles/' : 'load-articles/';
|
||||
const { error, data } = await useAPI<Article[]>(endpoint, { method: "GET" });
|
||||
|
||||
if (data.value) {
|
||||
articles.value = data.value;
|
||||
} else if (error.value) {
|
||||
console.error("Error loading articles:", error.value);
|
||||
}
|
||||
|
||||
</script>
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<v-parallax class="trainers__parallax" src="public/img/black-pink.jpg">
|
||||
<v-container id="trainers">
|
||||
<v-card class="trainers pa-4">
|
||||
<v-card-title class="about__title ">Naši trenéři a lektoři</v-card-title>
|
||||
<v-card-subtitle class="about__subtitle ">Seznamte se s námi!</v-card-subtitle>
|
||||
<v-container>
|
||||
<v-card class="about" id="trainers">
|
||||
<v-card-title class="about__title">Naši trenéři a lektoři</v-card-title>
|
||||
<v-card-subtitle class="about__subtitle">Seznamte se s námi!</v-card-subtitle>
|
||||
|
||||
<v-carousel cycle interval="4000" hide-delimiters show-arrows="hover">
|
||||
<v-carousel style="height: auto" cycle interval="4000" hide-delimiters :show-arrows="trainerGroups.length > 1 ? 'hover' : false">
|
||||
<v-carousel-item v-for="(group, index) in trainerGroups" :key="index">
|
||||
<v-row justify="center">
|
||||
<v-col
|
||||
|
@ -25,8 +25,8 @@
|
|||
>
|
||||
<v-img :src="lector.img" cover></v-img>
|
||||
</v-avatar>
|
||||
<v-card-subtitle class="about__subtitle">{{ lector.name }}</v-card-subtitle>
|
||||
<v-card-text class="about__text">{{ lector.desc }}</v-card-text>
|
||||
<div class="about__subtitle">{{ lector.name }}</div>
|
||||
<div class="about__text">{{ lector.desc }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-carousel-item>
|
||||
|
@ -64,6 +64,11 @@ const lectors = [
|
|||
img: "/trainers/img.png",
|
||||
desc: "Lektorka - tance pro děti",
|
||||
},
|
||||
{
|
||||
name: "Ondřej Gilar",
|
||||
img: "/trainers/img.png",
|
||||
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
||||
},
|
||||
]
|
||||
|
||||
const trainerGroups = computed(() => {
|
||||
|
|
|
@ -1,35 +1,46 @@
|
|||
<template>
|
||||
<v-card class="article__image">
|
||||
<v-card-title>{{ title }}
|
||||
<v-card-title>
|
||||
{{ images[model] && images[model].title }}
|
||||
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
|
||||
</v-card-title>
|
||||
|
||||
<v-carousel
|
||||
v-model="model"
|
||||
:hide-delimiters="true"
|
||||
height="90vh"
|
||||
>
|
||||
<v-carousel-item
|
||||
v-for="(item, i) in images"
|
||||
v-for="(item, i) in props.images"
|
||||
:key="i"
|
||||
:src="item.image"
|
||||
cover
|
||||
class="carousel__image"
|
||||
></v-carousel-item>
|
||||
/>
|
||||
</v-carousel>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import './assets/css/main.css';
|
||||
|
||||
interface ArticleImage {
|
||||
image: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
image: string;
|
||||
const props = defineProps<{
|
||||
image: ArticleImage;
|
||||
images: ArticleImage[];
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
defineEmits(["isActive"])
|
||||
defineEmits(["isActive"]);
|
||||
|
||||
const model = ref(0);
|
||||
|
||||
watch(() => props.image, (newImage) => {
|
||||
const index = props.images.findIndex((img) => img.image === newImage.image);
|
||||
if (index !== -1) model.value = index;
|
||||
}, { immediate: true });
|
||||
</script>
|
|
@ -9,13 +9,15 @@
|
|||
Taneční klub Ostrava
|
||||
</v-app-bar-title>
|
||||
|
||||
<v-tabs v-model="currentTab" class="d-none d-sm-flex app__tab">
|
||||
<v-tabs
|
||||
v-if="$vuetify.display.smAndUp"
|
||||
v-model="currentTabName"
|
||||
class="app__tab"
|
||||
>
|
||||
<v-tab
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:text="tab.name"
|
||||
:value="tab.name"
|
||||
:href="tab.href ? tab.href : undefined"
|
||||
@click="handleNavigation(tab)"
|
||||
>
|
||||
{{ tab.name }}
|
||||
|
@ -48,37 +50,93 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGoTo } from "~/composables/useGoTo";
|
||||
import { useRoute } from "#vue-router";
|
||||
import { computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const currentTab = ref({ name: "O nás", ref: "#about", href: "o-nas" });
|
||||
const router = useRouter();
|
||||
|
||||
const homeTabs = [
|
||||
{ name: "O nás", ref: "#about", href: "" },
|
||||
{ name: "Trenéři", ref: "#trainers", href: "" },
|
||||
{ name: "Kurzy", ref: "#courses", href: "" },
|
||||
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||
{ name: "Aktuality", ref: "#article", href: "" },
|
||||
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||
{ name: "O nás", href: "#about" },
|
||||
{ name: "Trenéři", href: "#trainers" },
|
||||
{ name: "Kurzy", href: "#courses" },
|
||||
{ name: "Galerie", href: "/galerie" },
|
||||
{ name: "Aktuality", href: "#article" },
|
||||
{ name: "Kontakty", href: "/kontakty" },
|
||||
];
|
||||
|
||||
const otherTabs = [
|
||||
{ name: "O nás", ref: "", href: "/" },
|
||||
{ name: "Trenéři", ref: "", href: "/" },
|
||||
{ name: "Kurzy", ref: "", href: "/" },
|
||||
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||
{ name: "Aktuality", ref: "", href: "/aktuality" },
|
||||
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||
{ name: "O nás", href: "/#about" },
|
||||
{ name: "Trenéři", href: "/#trainers" },
|
||||
{ name: "Kurzy", href: "/#courses" },
|
||||
{ name: "Galerie", href: "/galerie" },
|
||||
{ name: "Aktuality", href: "/aktuality" },
|
||||
{ name: "Kontakty", href: "/kontakty" },
|
||||
];
|
||||
|
||||
const tabs = computed(() => (route.path === "/" ? homeTabs : otherTabs));
|
||||
|
||||
const handleNavigation = (tab: any) => {
|
||||
if (tab.ref) {
|
||||
useGoTo(tab.ref);
|
||||
} else if (tab.href) {
|
||||
window.location.href = tab.href;
|
||||
type Tab = {
|
||||
name: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
const currentTab = ref<Tab>({ name: "O nás", href: "#about" });
|
||||
|
||||
const currentTabName = computed<string>({
|
||||
get: () => currentTab.value.name,
|
||||
set: (newName: string) => {
|
||||
const tab = tabs.value.find(t => t.name === newName);
|
||||
if (tab) handleNavigation(tab);
|
||||
}
|
||||
});
|
||||
|
||||
const handleNavigation = async (tab: { name: string, href: string }) => {
|
||||
currentTab.value = tab;
|
||||
|
||||
if (tab.href.startsWith("#")) {
|
||||
await scrollToHash(tab.href);
|
||||
} else if (tab.href.startsWith("/#")) {
|
||||
await router.push(tab.href);
|
||||
} else {
|
||||
await router.push(tab.href);
|
||||
}
|
||||
};
|
||||
|
||||
async function scrollToHash(hash: string) {
|
||||
await nextTick();
|
||||
|
||||
const el = document.querySelector(hash);
|
||||
if (el) {
|
||||
const yOffset = -80;
|
||||
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (route.path === "/" && route.hash) {
|
||||
const match = homeTabs.find(tab => tab.href === route.hash);
|
||||
if (match) {
|
||||
currentTab.value = match;
|
||||
}
|
||||
scrollToHash(route.hash);
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => route.hash, (newHash) => {
|
||||
if (route.path === "/" && newHash) {
|
||||
const match = homeTabs.find(tab => tab.href === newHash);
|
||||
if (match) currentTab.value = match;
|
||||
scrollToHash(newHash);
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => route.path, (newPath) => {
|
||||
if (newPath !== "/") {
|
||||
const match = otherTabs.find(tab => tab.href === newPath || tab.href === `${newPath}${route.hash}`);
|
||||
if (match) currentTab.value = match;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
</script>
|
|
@ -7,30 +7,35 @@ export async function useGoTo(
|
|||
): Promise<void> {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const yOffset = props?.offset ?? -80;
|
||||
const yOffset = props?.offset ?? -150;
|
||||
|
||||
const hash = selector.startsWith("#") ? selector : "";
|
||||
const path = selector.startsWith("/") ? selector : route.path + hash;
|
||||
const basePath = route.path.split("#")[0];
|
||||
const targetPath = selector.startsWith("/") ? selector : basePath + hash;
|
||||
const [pathOnly, hashOnly] = targetPath.split("#");
|
||||
|
||||
// If navigating to another page
|
||||
if (route.path !== path.split("#")[0]) {
|
||||
await router.push(path);
|
||||
await nextTick(); // Ensure DOM updates
|
||||
if (route.path !== pathOnly) {
|
||||
// Navigate to target page
|
||||
await router.push(pathOnly + (hashOnly ? `#${hashOnly}` : ""));
|
||||
await nextTick();
|
||||
|
||||
// Wait for the element to exist before scrolling
|
||||
if (hashOnly) {
|
||||
try {
|
||||
const element = await waitForElement(hash);
|
||||
const element = await waitForElement(`#${hashOnly}`);
|
||||
scrollToElement(element, yOffset);
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If already on the correct page, scroll immediately
|
||||
// Same page — scroll directly
|
||||
if (hashOnly || selector.startsWith("#")) {
|
||||
try {
|
||||
const element = await waitForElement(hash);
|
||||
const element = await waitForElement(`#${hashOnly ?? selector}`);
|
||||
scrollToElement(element, yOffset);
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,47 @@
|
|||
<template>
|
||||
<h1>Galerie</h1>
|
||||
<v-row>
|
||||
<v-col
|
||||
<div class="masonry-gallery">
|
||||
<div
|
||||
class="masonry-item"
|
||||
v-for="(photo, index) in gallery"
|
||||
:key="index"
|
||||
cols="4"
|
||||
style="padding: 0;"
|
||||
@click="openCarousel(photo)"
|
||||
>
|
||||
<v-img
|
||||
:lazy-src="photo.image"
|
||||
:src="photo.image"
|
||||
aspect-ratio="1"
|
||||
:lazy-src="photo.image"
|
||||
aspect-ratio=""
|
||||
class="article__image rounded-lg"
|
||||
cover
|
||||
@click="showCarousel = true"
|
||||
>
|
||||
<template v-slot:placeholder>
|
||||
<v-row>
|
||||
<v-progress-circular
|
||||
color="grey-lighten-5"
|
||||
indeterminate
|
||||
></v-progress-circular>
|
||||
<template #placeholder>
|
||||
<v-row justify="center" align="center" style="height: 100px;">
|
||||
<v-progress-circular indeterminate color="grey-lighten-1" />
|
||||
</v-row>
|
||||
</template>
|
||||
</v-img>
|
||||
</div>
|
||||
<v-dialog v-model="showCarousel">
|
||||
<dialog-carousel
|
||||
:image="photo.image"
|
||||
v-if="selectedImage"
|
||||
:image="selectedImage"
|
||||
:images="gallery"
|
||||
:title="photo.title"
|
||||
@isActive="showCarousel = false"
|
||||
/>
|
||||
</v-dialog>
|
||||
</v-img>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import './assets/css/main.css'
|
||||
import { useAPI } from "~/composables/useAPI";
|
||||
|
||||
const showCarousel = ref(false);
|
||||
const selectedImage = ref<ArticleImage | null>(null);
|
||||
|
||||
function openCarousel(photo: ArticleImage) {
|
||||
selectedImage.value = photo;
|
||||
showCarousel.value = true;
|
||||
}
|
||||
|
||||
interface ArticleImage {
|
||||
image: string;
|
||||
|
|
|
@ -62,6 +62,10 @@ export default defineNuxtPlugin((app) => {
|
|||
cs: {
|
||||
calendar: {
|
||||
today: "dnes",
|
||||
},
|
||||
carousel: {
|
||||
next: "další",
|
||||
prev: "předchozí",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue