Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 62 additions & 106 deletions packages/modules/web_themes/koala/source/src/components/BaseCarousel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ref="carouselRef"
v-model="currentSlide"
swipeable
:animated="animated"
:animated="true"
control-color="primary"
infinite
@update:model-value="handleSlideChange"
Expand All @@ -22,10 +22,10 @@
class="row no-wrap justify-center carousel-slide"
>
<div
v-for="(item, idx) in group"
v-for="item in group"
:key="item"
class="item-container"
:ref="idx === 0 && index === 0 ? 'itemRef' : undefined"
ref="itemRef"
>
<slot name="item" :item="item"></slot>
</div>
Expand All @@ -34,131 +34,87 @@
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted } from 'vue';
import { ref, computed, nextTick, onMounted, onBeforeUnmount, watch } from 'vue';
import { Screen } from 'quasar';

const props = defineProps<{
items: number[];
}>();
const props = defineProps<{ items: number[] }>();

const slideItemWidth = ref<number | null>(null);
const currentSlideItem = ref<number | null>(null);
const animated = ref<boolean>(true);
const carouselRef = ref<{ $el: HTMLElement } | null>(null);
const itemRef = ref<(HTMLElement | null)[]>([]);
const itemRef = ref<HTMLElement | null>(null);
const currentSlide = ref(0);

const showArrows = computed(() => {
return groupedItems.value.length > 1 && Screen.gt.xs;
});

const storeSlideItemWidth = (width: number) => {
console.log('Storing slide item width:', width);
slideItemWidth.value = width;
};
const itemWidth = ref(100);
const carouselWidth = ref(0);
const carouselPadding = ref(0);
const showArrows = ref(false);

const currentSlide = computed({
get: () => {
if (currentSlideItem.value == null) {
return 0; // Always show the first slide if a specific item is not set
function measure() {
nextTick(() => {
if (itemRef.value) {
itemWidth.value = itemRef.value[0].clientWidth || 300;
}
// Find the index of the slide containing the current item
let currentSlide = groupedItems.value.findIndex((group) =>
group.includes(currentSlideItem.value),
);
if (currentSlide === -1) {
return 0; // Default to first slide if item not found
showArrows.value = Screen.gt.xs && groupedItems.value.length > 1;
if (carouselRef.value?.$el) {
carouselWidth.value = carouselRef.value.$el.clientWidth || 0;
const slideEl = carouselRef.value.$el.querySelector('.q-carousel__slide');
if (slideEl) {
const style = window.getComputedStyle(slideEl);
carouselPadding.value =
parseFloat(style.paddingLeft || '0') +
parseFloat(style.paddingRight || '0');
} else {
console.warn('Could not find .q-carousel__slide element');
}
}
// Ensure the index is within bounds
return Math.min(currentSlide, groupedItems.value.length - 1);
},
set: (value: number) => {
// Update currentSlideItem to the first item of the new slide
currentSlideItem.value = groupedItems.value[value][0];
},
});
}


onMounted(() => {
measure();
window.addEventListener('resize', measure);
});

const groupSize = computed(() => {
let itemWidth = 300; // Default width
console.debug('Calculating group size...', itemRef.value);
if (slideItemWidth.value == null) {
if (
itemRef.value[0]?.clientWidth &&
itemRef.value[0].clientWidth != slideItemWidth.value
) {
storeSlideItemWidth(itemRef.value[0].clientWidth);
} else {
console.warn('Item width is not set, using default:', itemWidth);
}
} else {
itemWidth = slideItemWidth.value;
}
console.debug('Item width:', itemWidth);
let carouselSlideWidth = 0;
let padding = 0;
const slideEl = carouselRef.value?.$el.querySelector('.q-carousel__slide');
if (slideEl) {
carouselSlideWidth = slideEl.clientWidth ?? 0;
const style = window.getComputedStyle(slideEl);
padding =
parseFloat(style.paddingLeft || '0') +
parseFloat(style.paddingRight || '0');
}
const maxGroupSize = Math.max(
1,
Math.floor((carouselSlideWidth - padding) / itemWidth),
);
onBeforeUnmount(() => {
window.removeEventListener('resize', measure);
});

// Special case: Prevent a second group with only one item,
// if all items would fit side by side without navigation arrows
watch(() => props.items, measure);

const groupSize = computed(() => {
if (!itemWidth.value || !carouselWidth.value) return 1;
const maxGroup = Math.max(1, Math.floor((carouselWidth.value - (showArrows.value ? carouselPadding.value : 50)) / itemWidth.value));
// Spezialfall: Alle passen nebeneinander
if (
props.items.length > maxGroupSize &&
props.items.length <= maxGroupSize * 2 &&
props.items.length - maxGroupSize === 1
props.items.length > maxGroup &&
props.items.length <= maxGroup * 2 &&
props.items.length - maxGroup === 1
) {
// Check if all items would fit side by side
if (props.items.length * itemWidth <= carouselSlideWidth) {
if (props.items.length * itemWidth.value <= carouselWidth.value) {
return props.items.length;
}
}
return maxGroupSize;
return maxGroup;
});

const groupedItems = computed(() => {
return props.items.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(
index / (groupSize.value ? groupSize.value : props.items.length),
);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [];
}
resultArray[chunkIndex].push(item);
return resultArray;
}, [] as number[][]);
});

onMounted(async () => {
await nextTick(() => {
window.addEventListener('resize', () => {
// Trigger a re-render by resetting itemRef
itemRef.value = [...itemRef.value];
});
});
const size = groupSize.value;
const result: number[][] = [];
for (let i = 0; i < props.items.length; i += size) {
result.push(props.items.slice(i, i + size));
}
return result;
});

watch(
() => props.items,
() => {
// Reset itemRef to trigger re-render
itemRef.value = [...itemRef.value];
},
);
function handleSlideChange(val: number) {
currentSlide.value = val;
}

const handleSlideChange = () => {
const currentScroll = window.scrollY;
nextTick(() => {
window.scrollTo(0, currentScroll);
});
};
watch(groupedItems, (groups) => {
if (currentSlide.value > groups.length - 1) {
currentSlide.value = Math.max(0, groups.length - 1);
}
});
</script>

<style scoped>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<q-card
ref="cardRef"
class="card-width"
class="card-width full-height"
:class="{ 'battery-sum': props.batteryId === -1 }"
>
<q-card-section class="row items-center justify-between">
Expand Down Expand Up @@ -149,7 +149,7 @@ const dailyExportedEnergy = computed(() => {

<style scoped lang="scss">
.card-width {
max-width: 22em;
width: 22em;
}

.q-card__section {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-card ref="cardRef" class="card-width">
<q-card ref="cardRef" class="card-width full-height">
<q-card-section class="row no-wrap">
<div class="text-h6 text-bold ellipsis" :title="name">
{{ name }}
Expand Down Expand Up @@ -297,7 +297,7 @@ const refreshSoc = () => {
</script>
<style lang="scss" scoped>
.card-width {
max-width: 22em;
width: 22em;
}

.q-card__section {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-card ref="cardRef" class="card-width">
<q-card ref="cardRef" class="card-width full-height">
<q-card-section class="row">
<div class="text-h6 text-bold ellipsis" :title="vehicle?.name">
{{ vehicle?.name }}
Expand Down Expand Up @@ -93,7 +93,7 @@ const refreshSoc = () => {

<style lang="scss" scoped>
.card-width {
max-width: 22em;
width: 22em;
}

.q-card__section {
Expand Down