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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<q-carousel
ref="carouselRef"
v-model="currentSlide"
swipeable
:animated="animated"
Expand Down Expand Up @@ -33,24 +34,96 @@
</template>

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

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

const $q = useQuasar();
const currentSlide = ref<number>(0);
const animated = ref<boolean>(true);
const carouselRef = ref<{ $el: HTMLElement } | null>(null);
const carouselWidth = ref(0);
let resizeObserver: ResizeObserver | null = null;
const maxCardHeight = ref<number>(0);

// Calculates and sets the maximum card height for all cards in the active slide
const updateMaxCardHeight = () => {
// Quasar adds CSS class .q-carousel__slide--active when the slide is active - calculation then made only on active slide/s
const cards = document.querySelectorAll(
'.q-carousel__slide--active .item-container',
);
const heights = Array.from(cards).map(
(card) => (card as HTMLElement).offsetHeight,
);
maxCardHeight.value = Math.max(...heights);
};

/**
* Group the items in chunks of 2 for large screens and 1 for small screens.
*/
// Sets up a MutationObserver to watch for changes in the cards of the active slide
const observeCardChanges = () => {
const observer = new MutationObserver(() => {
updateMaxCardHeight();
});
const cards = document.querySelectorAll(
'.q-carousel__slide--active .item-container',
);
cards.forEach((card) => {
observer.observe(card, {
childList: true,
subtree: true,
attributes: true,
});
});
};

onMounted(() => {
nextTick(() => {
if (carouselRef.value && carouselRef.value.$el) {
carouselWidth.value = carouselRef.value.$el.offsetWidth;
// Set up ResizeObserver to update width and card height on resize
resizeObserver = new ResizeObserver(() => {
if (carouselRef.value && carouselRef.value.$el) {
carouselWidth.value = carouselRef.value.$el.offsetWidth;
updateMaxCardHeight();
}
});
resizeObserver.observe(carouselRef.value.$el);
}
// Calculate initial card height and set up MutationObserver
updateMaxCardHeight();
observeCardChanges();
});
});

onBeforeUnmount(() => {
if (resizeObserver && carouselRef.value && carouselRef.value.$el) {
resizeObserver.unobserve(carouselRef.value.$el);
}
});

const effectiveCardWidth = ref<number | undefined>(undefined);

// Computes how many cards can fit in the carousel based on carousel width and the card width
const groupSize = computed(() => {
return effectiveCardWidth.value
? Math.max(1, Math.floor(carouselWidth.value / effectiveCardWidth.value))
: 380;
});

// Groups the items into arrays for each slide, based on the computed group size
const groupedItems = computed(() => {
const groupSize = $q.screen.width > 800 ? 2 : 1;
return props.items.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / groupSize);
const chunkIndex = Math.floor(index / groupSize.value);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [];
}
Expand All @@ -59,11 +132,7 @@ const groupedItems = computed(() => {
}, [] as number[][]);
});

/**
* Update the current slide when the grouped items change.
* This may happen when the items are sorted or filtered or when the screen size changes.
* We try to keep the same item in view when the slide changes.
*/
// Updates the current slide and recalculates card heights when the grouped items change
watch(
() => groupedItems.value,
async (newValue, oldValue) => {
Expand All @@ -74,17 +143,19 @@ watch(
currentSlide.value = 0;
return;
}
// Prevent animation when the current slide is modified
animated.value = false;
currentSlide.value = Math.max(
findSlide(oldValue[currentSlide.value][0]),
0,
);
await nextTick();
animated.value = true;
updateMaxCardHeight();
observeCardChanges();
},
);

// Called when the slide changes; recalculates card heights and scrolls to the previous position
const handleSlideChange = () => {
const currentScroll = window.scrollY;
nextTick(() => {
Expand All @@ -94,44 +165,13 @@ const handleSlideChange = () => {
});
};

const maxCardHeight = ref<number>(0);

const updateMaxCardHeight = () => {
const cards = document.querySelectorAll('.item-container');
const heights = Array.from(cards).map(
(card) => (card as HTMLElement).offsetHeight,
);
maxCardHeight.value = Math.max(...heights);
};

const observeCardChanges = () => {
const observer = new MutationObserver(() => {
updateMaxCardHeight();
});
const cards = document.querySelectorAll('.item-container');
cards.forEach((card) => {
observer.observe(card, {
childList: true,
subtree: true,
attributes: true,
});
});
};

onMounted(() => {
nextTick(() => {
updateMaxCardHeight();
observeCardChanges();
});
});

// watches cardWidth prop because it takes time to be emitted and passed through component hierarchy
watch(
() => props.items,
() => {
nextTick(() => {
updateMaxCardHeight();
observeCardChanges();
});
() => props.cardWidth,
(newVal) => {
if (newVal && newVal > 0) {
effectiveCardWidth.value = newVal + 72; // Add 72px to account for padding / margins / navigation buttons in carousel
}
},
);
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-card class="full-height card-width">
<q-card ref="cardRef" class="full-height card-width">
<q-card-section>
<div class="row text-h6 items-center text-bold justify-between">
<div>
Expand Down Expand Up @@ -62,12 +62,17 @@
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import BatterySettingsDialog from './BatterySettingsDialog.vue';
import { useBatteryModes } from 'src/composables/useBatteryModes.ts';
import SliderDouble from './SliderDouble.vue';

const cardRef = ref<{ $el: HTMLElement } | null>(null);
const emit = defineEmits<{
(event: 'card-width', width: number | undefined): void;
}>();

const props = defineProps<{
batteryId: number | undefined;
}>();
Expand Down Expand Up @@ -131,6 +136,11 @@ const dailyExportedEnergy = computed(() => {
'---'
);
});

onMounted(() => {
const cardWidth = cardRef.value?.$el.clientWidth;
emit('card-width', cardWidth);
});
</script>

<style scoped>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<template>
<div v-if="showBatteryOverview" class="row justify-center">
<BatteryCard :battery-id="undefined" />
<BatteryCard :battery-id="undefined"/>
</div>
<BaseCarousel :items="batteryIds">
<BaseCarousel :items="batteryIds" :card-width="cardWidth">
<template #item="{ item }">
<BatteryCard :battery-id="item" />
<BatteryCard :battery-id="item" @card-width="cardWidth = $event"/>
</template>
</BaseCarousel>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';

import BaseCarousel from 'src/components/BaseCarousel.vue';
import BatteryCard from 'src/components/BatteryCard.vue';

const cardWidth = ref<number | undefined>(undefined);

const mqttStore = useMqttStore();

const batteryIds = computed(() => mqttStore.batteryIds);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-card class="full-height card-width">
<q-card ref="cardRef" class="full-height card-width">
<q-card-section>
<div class="row items-center text-h6 text-bold">
<div class="col flex items-center">
Expand Down Expand Up @@ -93,7 +93,7 @@
/>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import SliderDouble from './SliderDouble.vue';
import ChargePointLock from './ChargePointLock.vue';
Expand All @@ -109,6 +109,11 @@ import ChargePointTimeCharging from './ChargePointTimeCharging.vue';
import ChargePointPowerData from './ChargePointPowerData.vue';
import { useQuasar } from 'quasar';

const cardRef = ref<{ $el: HTMLElement } | null>(null);
const emit = defineEmits<{
(event: 'card-width', width: number | undefined): void;
}>();

const mqttStore = useMqttStore();

const $q = useQuasar();
Expand Down Expand Up @@ -278,6 +283,11 @@ const refreshSoc = () => {
message: 'SoC Update angefordert.',
});
};

onMounted(() => {
const cardWidth = cardRef.value?.$el.offsetWidth;
emit('card-width', cardWidth);
});
</script>
<style lang="scss" scoped>
.card-width {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<BaseCarousel
v-if="chargePointIds.length <= cardViewBreakpoint"
:items="chargePointIds"
:card-width="cardWidth"
>
<template #item="{ item }">
<ChargePointCard :charge-point-id="item" />
<ChargePointCard :charge-point-id="item" @card-width="cardWidth = $event" />
</template>
</BaseCarousel>

Expand Down Expand Up @@ -145,6 +146,8 @@ import {
ChargePointRow,
} from 'src/components/models/table-model';

const cardWidth = ref<number | undefined>(undefined);

const mqttStore = useMqttStore();
const { chargeModes } = useChargeModes();
const chargePointIds = computed(() => mqttStore.chargePointIds);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<q-card class="full-height card-width">
<q-card ref="cardRef" class="full-height card-width">
<q-card-section>
<div class="row items-center text-h6 text-bold">
<div class="col flex items-center">
Expand Down Expand Up @@ -59,13 +59,18 @@
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import { useQuasar } from 'quasar';
import SliderDouble from './SliderDouble.vue';
import ManualSocDialog from './ManualSocDialog.vue';
import VehicleConnectionStateIcon from './VehicleConnectionStateIcon.vue';

const cardRef = ref<{ $el: HTMLElement } | null>(null);
const emit = defineEmits<{
(event: 'card-width', width: number | undefined): void;
}>();

const props = defineProps<{
vehicleId: number;
}>();
Expand Down Expand Up @@ -97,6 +102,11 @@ const refreshSoc = () => {
message: 'SoC Update angefordert.',
});
};

onMounted(() => {
const cardWidth = cardRef.value?.$el.offsetWidth;
emit('card-width', cardWidth);
});
</script>

<style lang="scss" scoped>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<BaseCarousel
v-if="vehicleIds.length <= cardViewBreakpoint"
:items="vehicleIds"
:card-width="cardWidth"
>
<template #item="{ item }">
<VehicleCard :vehicle-id="item" />
<VehicleCard :vehicle-id="item" @card-width="cardWidth = $event" />
</template>
</BaseCarousel>

Expand Down Expand Up @@ -73,6 +74,8 @@ import VehicleConnectionStateIcon from './VehicleConnectionStateIcon.vue';
import VehicleCard from 'src/components/VehicleCard.vue';
import { ColumnConfiguration } from 'src/components/models/table-model';

const cardWidth = ref<number | undefined>(undefined);

const mqttStore = useMqttStore();
const isMobile = computed(() => Platform.is.mobile);
const modalChargeVehicleCardVisible = ref(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,9 @@ const chartOptions = computed(() => ({
flex: 1;
min-height: 0;
}

.chart-wrapper > canvas {
width: 100% !important;
height: 100% !important;
}
</style>
Loading