mouvement et lock de la carte + prevent refresh homepage

This commit is contained in:
Valentin 2024-10-07 23:39:50 +02:00
parent 6dad6cc7bc
commit 80f7f43370
7 changed files with 225 additions and 96 deletions

View File

@ -7,6 +7,7 @@ import VueImageZoomer from 'vue-image-zoomer'
import 'vue-image-zoomer/dist/style.css'; import 'vue-image-zoomer/dist/style.css';
import { useContentStore } from './stores/content'; import { useContentStore } from './stores/content';
import { useMapStore } from './stores/mapState';
import router from './router/router'; import router from './router/router';
// Working with the history API // Working with the history API
@ -31,19 +32,13 @@ import router from './router/router';
(function ($, Drupal, drupalSettings) { (function ($, Drupal, drupalSettings) {
const CaravaneTheme = function () { const CaravaneTheme = function () {
const _is_front = drupalSettings.path.isFront const _is_front = drupalSettings.path.isFront;
console.log('drupalSettings', drupalSettings) console.log('drupalSettings', drupalSettings);
// let _I18n
// ___ _ _
// |_ _|_ _ (_) |_
// | || ' \| | _|
// |___|_||_|_|\__|
function init () { function init () {
console.log('CaravaneTheme init()') console.log('CaravaneTheme init()');
initVues() initVues();
toggleMenu() toggleMenu();
} }
function initVues(){ function initVues(){
@ -55,45 +50,67 @@ import router from './router/router';
.use(createPinia()).use(router) .use(createPinia()).use(router)
.use(VueImageZoomer); .use(VueImageZoomer);
const store = useContentStore(); const store = useContentStore();
const mapStore = useMapStore();
app.mount('#content-modale'); app.mount('#content-modale');
processEtapeLinks(store); Drupal.behaviors.customLeafletInteraction = {
processStaticLinks(store); attach: function(context, settings) {
$(context).on('leafletMapInit', function (e, settings, map, mapid, markers) {
setupEtapeMapPopup(store); mapStore.defaultMapCenter = map.getCenter();
mapStore.maxZoom = settings.settings.maxZoom;
mapStore.defaultZoom = settings.settings.minZoom;
processEtapeLinks(store, map);
processStaticLinks(store, map);
processHeaderLogo(store, map);
setupEtapeMapPopup(store, map);
});
}
}
} }
function onClickContentLink(e, store, map, category){
function onClickContentLink(e, store, category){
e.preventDefault(); e.preventDefault();
let a; let a;
const li = e.target.closest('li'); if (e.target.tagName !== 'IMG') {
a = li.querySelector('a'); const li = e.target.closest('li');
a = li.querySelector('a');
} else {
a = e.target.closest('a');
}
let nid = a.dataset.nodeNid; let nid = a.dataset.nodeNid;
if (category === 'etape') { if (category === 'etape') {
store.fetchEtapeData(nid); store.fetchEtapeData(nid, map);
} else if (category === 'static') { } else if (category === 'static') {
store.fetchStaticData(nid); if (nid) {
store.fetchStaticData(nid, map);
} else {
store.emptyAll(null, map);
}
} }
return null; return null;
} }
function processStaticLinks(store){ function processStaticLinks(store, map) {
let general_link_fields = document.querySelectorAll('#menu > ul > li:not(:first-of-type) > a'); let general_link_fields = document.querySelectorAll('#menu > ul > li > a');
for (let field of general_link_fields) { for (let field of general_link_fields) {
let general_link_href = field.getAttribute('href'); let general_link_href = field.getAttribute('href');
const nid = general_link_href.charAt(general_link_href.length-1); const nid = general_link_href.charAt(general_link_href.length-1);
field.setAttribute('data-node-nid', nid); field.setAttribute('data-node-nid', parseInt(nid));
field.addEventListener('click', (e) => onClickContentLink(e, store, 'static')); field.addEventListener('click', (e) => onClickContentLink(e, store, map, 'static'));
} }
} }
function processHeaderLogo(store, map) {
const logo = document.querySelector('#block-caravane-logocaravane a');
logo.addEventListener('click', (e) => onClickContentLink(e, store, map, 'static'));
}
function processEtapeLinks(store){ function processEtapeLinks(store, map) {
let etape_li = document.querySelectorAll('#etapes-liste li'); let etape_li = document.querySelectorAll('#etapes-liste li');
etape_li.forEach((li) => { etape_li.forEach((li) => {
let field = li.querySelector('div.views-field-title'); let field = li.querySelector('div.views-field-title');
@ -110,7 +127,7 @@ import router from './router/router';
if (nid) { if (nid) {
let a = field.querySelector('a'); let a = field.querySelector('a');
a.setAttribute('data-node-nid', nid); a.setAttribute('data-node-nid', nid);
li.addEventListener('click', (e) => onClickContentLink(e, store, 'etape')); li.addEventListener('click', (e) => onClickContentLink(e, store, map, 'etape'));
} }
let couleur = li.querySelector('.views-field-field-couleur .snippets-description').innerText; let couleur = li.querySelector('.views-field-field-couleur .snippets-description').innerText;
let iconElements = li.querySelectorAll('.icone-arret > div'); let iconElements = li.querySelectorAll('.icone-arret > div');
@ -145,52 +162,43 @@ import router from './router/router';
}) })
} }
function setupEtapeMapPopup(store) { function setupEtapeMapPopup(store, map) {
Drupal.behaviors.customLeafletInteraction = { const icons = document.querySelectorAll('.leaflet-map-divicon');
attach: function(context, settings) { for (let icon of icons) {
$(context).on('leafletMapInit', function (e, settings, map, mapid, markers) { const colorContainer = icon.querySelector('.couleur');
const icons = document.querySelectorAll('.leaflet-map-divicon'); let colorDivs = colorContainer.querySelectorAll('.separated-content');
for (let icon of icons) { let color;
const colorContainer = icon.querySelector('.couleur'); colorDivs.forEach((div) => {
let colorDivs = colorContainer.querySelectorAll('.separated-content'); if (div.innerText.startsWith('<div class="snippets-description">')) {
let color; color = div.innerText;
colorDivs.forEach((div) => { }
if (div.innerText.startsWith('<div class="snippets-description">')) { });
color = div.innerText; color = color.substring(color.indexOf('>') + 1, color.indexOf('<', color.indexOf('>') + 1)).trim();
}
});
color = color.substring(color.indexOf('>') + 1, color.indexOf('<', color.indexOf('>') + 1)).trim();
const nid = icon.querySelector('.nid'); const nid = icon.querySelector('.nid');
const nidValue = nid.querySelector('.separated-content').innerText; const nidValue = nid.querySelector('.separated-content').innerText;
icon.addEventListener('click', function(event) { icon.addEventListener('click', function(event) {
store.fetchEtapeData(nidValue); store.fetchEtapeData(nidValue, map);
}); });
//colorContainer.remove(); colorContainer.style.display = "none";
//nid.remove(); nid.style.display = "none";
colorContainer.style.display = "none"; const iconElements = icon.querySelectorAll('div');
nid.style.display = "none"; for (let iconElement of iconElements) {
const iconElements = icon.querySelectorAll('div'); iconElement.style.backgroundColor = color;
for (let iconElement of iconElements) {
iconElement.style.backgroundColor = color;
}
icon.removeAttribute('title');
icon.addEventListener('mouseenter', function (event) {
icon.style.transform = `${icon.style.transform} scale(1.1)`;
const popup = document.querySelector('.leaflet-tooltip-center > div');
popup.style.opacity = "1";
});
icon.addEventListener('mouseleave', function (event) {
icon.style.transform = icon.style.transform.split(' ')[0] + icon.style.transform.split(' ')[1] + icon.style.transform.split(' ')[2];
})
}
});
} }
icon.removeAttribute('title');
icon.addEventListener('mouseenter', function (event) {
icon.style.transform = `${icon.style.transform} scale(1.1)`;
const popup = document.querySelector('.leaflet-tooltip-center > div');
popup.style.opacity = "1";
});
icon.addEventListener('mouseleave', function (event) {
icon.style.transform = icon.style.transform.split(' ')[0] + icon.style.transform.split(' ')[1] + icon.style.transform.split(' ')[2];
})
} }
} }

View File

@ -6,9 +6,11 @@ import REST from '../api/rest-axios';
export const useContentStore = defineStore('content', { export const useContentStore = defineStore('content', {
state: () => ({ state: () => ({
href: '', href: '',
map: {},
etape: { etape: {
title: '', title: '',
adresse: {}, adresse: {},
coordinates: {},
etape_number: '', etape_number: '',
vignette: {}, vignette: {},
couleur: '', couleur: '',
@ -37,8 +39,9 @@ export const useContentStore = defineStore('content', {
error: null, error: null,
}), }),
actions: { actions: {
async fetchEtapeData(nid) { async fetchEtapeData(nid, map) {
this.resetStore(); this.resetStore();
this.map = map;
try { try {
const response = await REST.get(`/jsonapi/node/etape/`); const response = await REST.get(`/jsonapi/node/etape/`);
for (let etape of response.data.data) { for (let etape of response.data.data) {
@ -48,6 +51,10 @@ export const useContentStore = defineStore('content', {
this.href = metatag.attributes.href; this.href = metatag.attributes.href;
} }
} }
this.etape.coordinates = {
lat: etape.attributes.field_geofield.lat,
lon: etape.attributes.field_geofield.lon,
};
this.etape.title = etape.attributes.title; this.etape.title = etape.attributes.title;
this.etape.adresse = etape.attributes.field_adresse; this.etape.adresse = etape.attributes.field_adresse;
this.etape.etape_number = etape.attributes.field_arret_numero; this.etape.etape_number = etape.attributes.field_arret_numero;
@ -205,8 +212,9 @@ export const useContentStore = defineStore('content', {
this.loading = false; this.loading = false;
} }
}, },
async fetchStaticData(nid) { async fetchStaticData(nid, map) {
this.resetStore(); this.resetStore();
this.map = map;
try { try {
const response = await REST.get(`/jsonapi/node/static/`); const response = await REST.get(`/jsonapi/node/static/`);
for (let page of response.data.data) { for (let page of response.data.data) {
@ -285,7 +293,9 @@ export const useContentStore = defineStore('content', {
} }
} }
}, },
emptyAll(nid) { emptyAll(nid, map) {
this.href = '';
this.map = map;
this.etape = {}; this.etape = {};
this.page = {}; this.page = {};
this.setActiveItemInMenu(nid); this.setActiveItemInMenu(nid);
@ -328,7 +338,6 @@ export const useContentStore = defineStore('content', {
resetStore() { resetStore() {
this.loading = true; this.loading = true;
this.error = null; this.error = null;
this.href = '';
this.etape = {}; this.etape = {};
this.page = {}; this.page = {};
}, },

View File

@ -0,0 +1,46 @@
import { defineStore } from 'pinia';
export const useMapStore = defineStore('mapState', {
state: () => ({
defaultZoom: Number,
defaultMapCenter: Object,
currentPlace: Object,
maxZoom: Number,
currentZoom: Number,
duration: 3,
}),
actions: {
zoomToPlace(map, lat, long) {
map.flyTo([lat, long], this.maxZoom, { duration: this.duration });
this.currentZoom = this.maxZoom;
},
resetMap(map) {
map.flyTo(this.defaultMapCenter, this.defaultZoom, { duration: this.duration });
this.currentZoom = this.defaultZoom;
},
lockMap(map) {
setTimeout(() => {
map.options.minZoom = this.currentZoom;
map.options.maxZoom = this.currentZoom;
}, this.duration * 1000 + 100);
map.dragging.disable();
map.touchZoom.disable();
map.doubleClickZoom.disable();
map.scrollWheelZoom.disable();
map.boxZoom.disable();
map.keyboard.disable();
// map.tap.disable();
},
unlockMap(map) {
map.options.minZoom = this.defaultZoom;
map.options.maxZoom = this.maxZoom;
map.dragging.enable();
map.touchZoom.enable();
map.doubleClickZoom.enable();
map.scrollWheelZoom.enable();
map.boxZoom.enable();
map.keyboard.enable();
// map.tap.enable();
},
},
});

View File

@ -20,7 +20,8 @@
:couleur="etape.couleur || brandColor" /> :couleur="etape.couleur || brandColor" />
<ModaleDiaporama <ModaleDiaporama
v-if="partie.type === 'diaporama'" v-if="partie.type === 'diaporama'"
:partie="partie" /> :partie="partie"
:couleur="etape.couleur || brandColor" />
<ModaleEntretien <ModaleEntretien
v-if="partie.type === 'entretien'" v-if="partie.type === 'entretien'"
:partie="partie" :partie="partie"
@ -36,7 +37,8 @@
</main> </main>
<ModaleFooter <ModaleFooter
:content="etape || page" :content="etape || page"
:couleur="etape.couleur || brandColor" /> :couleur="etape.couleur || brandColor"
:map="map" />
</div> </div>
</div> </div>
</Transition> </Transition>
@ -46,6 +48,7 @@
import { computed, watch, onMounted } from 'vue'; import { computed, watch, onMounted } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useContentStore } from '../stores/content'; import { useContentStore } from '../stores/content';
import { useMapStore } from '../stores/mapState';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import ModaleHeader from './components/ModaleHeader.vue'; import ModaleHeader from './components/ModaleHeader.vue';
@ -64,13 +67,17 @@ const { isObjectEmpty, scrollTop } = useUtils();
const router = useRouter(); const router = useRouter();
const store = useContentStore(); const store = useContentStore();
const mapState = useMapStore();
const route = useRoute(); const route = useRoute();
const { loading, error, href, etape, page } = storeToRefs(store); const { loading, error, href, map, etape, page } = storeToRefs(store);
const { duration } = storeToRefs(mapState);
const isEtapeValid = computed(() => !error.value && !loading.value && etape.value && !isObjectEmpty(etape.value)); const isEtapeValid = computed(() => !error.value && !loading.value && etape.value && !isObjectEmpty(etape.value));
const isPageValid = computed(() => !error.value && !loading.value && page.value && !isObjectEmpty(page.value)); const isPageValid = computed(() => !error.value && !loading.value && page.value && !isObjectEmpty(page.value));
let isModaleEtape, wasModaleEtape;
const brandColor = "#80c8bf"; const brandColor = "#80c8bf";
let isProgrammaticNavigation = false; let isProgrammaticNavigation = false;
@ -78,18 +85,18 @@ let isProgrammaticNavigation = false;
const handleRouteChange = () => { const handleRouteChange = () => {
watch( watch(
() => route.params.id, () => route.params.id,
(newId) => { (newId) => {
if (isProgrammaticNavigation) { if (isProgrammaticNavigation) {
isProgrammaticNavigation = false; isProgrammaticNavigation = false;
return; return;
} }
if (!newId) { if (!newId) {
store.emptyAll(); store.emptyAll(map.value);
} else { } else {
store.fetchEtapeData(newId); store.fetchEtapeData(newId, map.value);
if (!etape.value?.data) { if (!etape.value?.data) {
store.fetchStaticData(newId); store.fetchStaticData(newId, map.value);
} }
scrollTop(); scrollTop();
} }
@ -112,36 +119,82 @@ const handleHrefChange = () => {
() => href.value, () => href.value,
(newHref) => { (newHref) => {
const relativePath = newHref.split('.fr')[1]; const relativePath = newHref.split('.fr')[1];
if (relativePath && relativePath !== '' && relativePath !== '/') { isProgrammaticNavigation = true;
isProgrammaticNavigation = true; if (newHref == '') {
router.push(relativePath); router.push('/');
scrollTop(); mapState.unlockMap(map.value)
} else {
if (relativePath && relativePath !== '' && relativePath !== '/') {
mapState.lockMap(map.value);
router.push(relativePath);
scrollTop();
}
} }
} }
); );
}; };
const handleMapMovement = () => {
watch(
() => href.value,
() => {
console.log("NEW HREF");
console.log(href.value);
isModaleEtape = !isObjectEmpty(etape.value);
console.log("CAS 1", !wasModaleEtape && isModaleEtape);
console.log("CAS 2", wasModaleEtape && isModaleEtape);
console.log("CAS 3", wasModaleEtape && !isModaleEtape);
if (!wasModaleEtape && isModaleEtape) {
document.documentElement.style.setProperty('--modale-enter-delay', `${duration.value}s`);
mapState.zoomToPlace(map.value, etape.value.coordinates.lat, etape.value.coordinates.lon);
} else if (wasModaleEtape && isModaleEtape) {
document.documentElement.style.setProperty('--modale-leave-delay', 0);
document.documentElement.style.setProperty('--modale-enter-delay', `${duration.value * 2}s`);
mapState.resetMap(map.value);
setTimeout(() => {
mapState.zoomToPlace(map.value, etape.value.coordinates.lat, etape.value.coordinates.lon);
}, duration.value * 1000);
} else if (wasModaleEtape && !isModaleEtape) {
document.documentElement.style.setProperty('--modale-leave-delay', 0);
mapState.resetMap(map.value);
}
wasModaleEtape = isModaleEtape;
},
);
};
onMounted(() => { onMounted(() => {
isModaleEtape = !isObjectEmpty(etape.value);
wasModaleEtape = isModaleEtape;
handleRouteChange(); handleRouteChange();
handleColorChange(); handleColorChange();
handleHrefChange(); handleHrefChange();
handleMapMovement();
}); });
</script> </script>
<style scss> <style scss>
.v-enter-active, .v-enter-active {
transition: all 0.5s linear var(--modale-enter-delay);
}
.v-leave-active { .v-leave-active {
transition: all 0.3s ease; transition: all 0.5s linear var(--modale-leave-delay);
} }
.v-enter-from, .v-enter-from,
.v-leave-to { .v-leave-to {
transform: translateY(100%); transform: translateY(20vh);
opacity: 0;
} }
.v-enter-to,
.v-leave-from { .v-leave-from {
transform: translateY(0%); transform: translateY(0vh);
opacity: 1;
} }
</style> </style>

View File

@ -5,7 +5,7 @@
</div> </div>
<div v-if="content.previous || content.next" class="related-etape-links"> <div v-if="content.previous || content.next" class="related-etape-links">
<div v-if="content.previous" class="card previous" @click="store.fetchEtapeData(content.previous.nid)"> <div v-if="content.previous" class="card previous" @click="store.fetchEtapeData(content.previous.nid, map)">
<div class="icon"> <div class="icon">
<div :style="{ backgroundColor: content.previous.couleur }"></div> <div :style="{ backgroundColor: content.previous.couleur }"></div>
<div :style="{ backgroundColor: content.previous.couleur }"></div> <div :style="{ backgroundColor: content.previous.couleur }"></div>
@ -21,7 +21,7 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="content.next" class="card next" @click="store.fetchEtapeData(content.next.nid)"> <div v-if="content.next" class="card next" @click="store.fetchEtapeData(content.next.nid, map)">
<div class="icon"> <div class="icon">
<div :style="{ backgroundColor: content.next.couleur }"></div> <div :style="{ backgroundColor: content.next.couleur }"></div>
<div :style="{ backgroundColor: content.next.couleur }"></div> <div :style="{ backgroundColor: content.next.couleur }"></div>
@ -50,5 +50,6 @@ const store = useContentStore();
const props = defineProps({ const props = defineProps({
content: Object, content: Object,
couleur: String, couleur: String,
map: Object,
}); });
</script> </script>

View File

@ -26,6 +26,7 @@
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue';
import { useImageModal } from '../../composables/useImageModale'; import { useImageModal } from '../../composables/useImageModale';
import ImageModale from '../ImageModale.vue'; import ImageModale from '../ImageModale.vue';
// WebComponent // WebComponent
@ -34,7 +35,8 @@ import { register } from 'swiper/element/bundle';
register(); register();
const props = defineProps({ const props = defineProps({
partie: Object partie: Object,
couleur: String,
}); });
const { const {
@ -62,6 +64,10 @@ const handleImageClick = (event) => {
openImageModale(img.src, img.alt, swiperMedia); openImageModale(img.src, img.alt, swiperMedia);
} }
}; };
onMounted(() => {
document.documentElement.style.setProperty('--etape-couleur', props.couleur);
});
</script> </script>
<style> <style>

View File

@ -1,6 +1,12 @@
<template> <template>
<div class="videos"> <div class="videos">
<iframe v-for="video in partie.videos" :src="video" frameborder="0" width="100%" style="aspect-ratio: 16 / 9;"></iframe> <iframe
v-for="video in partie.videos"
:src="video"
frameborder="0"
width="100%"
style="aspect-ratio: 16 / 9;">
</iframe>
</div> </div>
</template> </template>