refactor du système de routing (EXPORTS DES SETTINGS DRUPAL)

This commit is contained in:
Valentin 2024-10-17 02:50:39 +02:00
parent 74f099ebdd
commit d5c5d81841
20 changed files with 613 additions and 712 deletions

View File

@ -0,0 +1,18 @@
uuid: f904b753-75c5-477e-bd9c-7259f9bfdf9b
langcode: fr
status: false
dependencies:
module:
- path_alias
- serialization
- user
id: entity.path_alias
plugin_id: 'entity:path_alias'
granularity: resource
configuration:
methods:
- GET
formats:
- json
authentication:
- cookie

View File

@ -269,7 +269,7 @@ display:
popupAnchor:
x: ''
'y': ''
html: "<div></div>\r\n<div></div>\r\n<div></div>\r\n<div class=\"nid\">{{ nid }}</div>\r\n<div class=\"couleur\">{{ field_couleur }}</div>"
html: "<div></div>\r\n<div></div>\r\n<div></div>\r\n<div class=\"url\">[node:url]</div>\r\n<div class=\"couleur\">[node:field_couleur]</div>"
html_class: 'leaflet-map-divicon '
circle_marker_options: '{"radius":100,"color":"red","fillColor":"#f03","fillOpacity":0.5}'
leaflet_markercluster:

View File

@ -1,253 +1,49 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { initVueContentModale } from './utils/vue-setup';
import { processClickableElements } from './utils/process-clickable-elements';
import { setMenuToggle, setHamburgerWhenLogged } from './utils/layout-setup';
import { initFirstLoadRouting, handleClickableElements } from './utils/handle-navigation';
import { setupMapStore } from './utils/map-setup';
import '../scss/main.scss'
import Modale from './vuejs/Modale.vue'
import VueImageZoomer from 'vue-image-zoomer'
import 'vue-image-zoomer/dist/style.css';
import { useContentStore } from './stores/content';
import { useMapStore } from './stores/mapState';
import router from './router/router';
// Working with the history API
// https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API
// /**
// * @file
// * reha behaviors.
// * https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview
// */
// (function (Drupal) {
// 'use strict';
// Drupal.behaviors.reha = {
// attach: function (context, settings) {
// console.log('It works!');
// }
// };
// } (Drupal));
// https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview
(function ($, Drupal, drupalSettings) {
const CaravaneTheme = function () {
const _is_front = drupalSettings.path.isFront;
console.log('drupalSettings', drupalSettings);
const CaravaneTheme = function () {
function init () {
console.log('DrupalSettings', drupalSettings);
function init () {
console.log('CaravaneTheme init()');
initVues();
toggleMenu();
}
const baseUrl = window.location.protocol + "//" + window.location.host;
const siteName = document.querySelector('#site_name').innerText;
const { store, mapStore, router, route } = initVueContentModale();
function initVues(){
initVueContentModale();
setMenuToggle();
setHamburgerWhenLogged(drupalSettings);
}
// https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/leaflet/leaflet-api
function initVueContentModale(){
const app = createApp(Modale)
.use(createPinia()).use(router)
.use(VueImageZoomer);
const store = useContentStore();
const mapStore = useMapStore();
app.mount('#content-modale');
Drupal.behaviors.customLeafletInteraction = {
attach: function(context, settings) {
$(context).on('leafletMapInit', function (e, settings, map, mapid, markers) {
const {
etapeListLinks,
generalListLinks,
logoLink,
mapIcons,
} = processClickableElements();
const clickableElements = [...etapeListLinks, ...generalListLinks, logoLink, ...mapIcons];
setHamburgerWhenLogged();
setupMapStore(mapStore, map, settings);
Drupal.behaviors.customLeafletInteraction = {
attach: function(context, settings) {
$(context).on('leafletMapInit', function (e, settings, map, mapid, markers) {
mapStore.defaultMapCenter = map.getCenter();
mapStore.maxZoom = settings.settings.maxZoom;
mapStore.defaultZoom = settings.settings.minZoom;
initFirstLoadRouting(store, router, baseUrl, siteName);
initFirstLoadRouting(store, map);
processEtapeLinks(store, map);
processStaticLinks(store, map);
processHeaderLogo(store, map);
setupEtapeMapPopup(store, map);
});
}
}
}
function initFirstLoadRouting(store, map){
var decoupled_origin = JSON.parse(window.localStorage.getItem('decoupled_origin'));
console.log('decoupled_origin', decoupled_origin);
if(decoupled_origin && decoupled_origin.entity_id){
// Si c'était moi je ne ferais qu'une seule function fetchdata capable de dealer avec les différent type de contenus
switch (decoupled_origin.entity_bundle) {
case 'etape':
store.fetchEtapeData(decoupled_origin.entity_id, map);
break;
case 'static':
store.fetchEtapeData(decoupled_origin.entity_id, map);
break;
handleClickableElements(clickableElements, store, router, baseUrl, siteName, mapStore);
});
}
}
}
router.push({
// name: decoupled_origin.entity_bundle,
path: decoupled_origin.url,
// params: {
// title: decoupled_origin.entity_uuid
// },
// props: {
// nid: decoupled_origin.entity_id
// }
});
// reset the storage
window.localStorage.removeItem("decoupled_origin");
}
init()
}
function onClickContentLink(e, store, map, category){
e.preventDefault();
let a;
if (e.target.tagName !== 'IMG') {
const li = e.target.closest('li');
a = li.querySelector('a');
} else {
a = e.target.closest('a');
}
let nid = a.dataset.nid;
if (category === 'etape') {
store.fetchEtapeData(nid, map);
} else if (category === 'static') {
if (nid) {
store.fetchStaticData(nid, map);
} else {
store.emptyAll(null, map);
}
}
return null;
}
function processStaticLinks(store, map) {
let general_link_fields = document.querySelectorAll('#menu > ul > li > a');
for (let i =1; i < general_link_fields.length; i ++) {
let general_link_path = general_link_fields[i].getAttribute('data-drupal-link-system-path');
const match = [...general_link_path.match(/^node\/(\d+)$/)];
if (match) {
const nid = match[1];
general_link_fields[i].setAttribute('data-nid', parseInt(nid));
general_link_fields[i].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, map) {
let etape_li = document.querySelectorAll('#etapes-liste li');
etape_li.forEach((li) => {
let etape_link = li.querySelector('a.etape-link');
let nid = etape_link.dataset.nid;
if (nid) {
li.addEventListener('click', (e) => onClickContentLink(e, store, map, 'etape'));
}
let couleur = etape_link.dataset.couleur;
let iconElements = li.querySelectorAll('.icone-arret > div');
for (let element of iconElements) {
element.style.backgroundColor = couleur;
}
})
}
function toggleMenu() {
const menuButton = document.querySelector('#block-caravane-mainnavigation > #menu');
const menuContainer = document.querySelector('#block-caravane-mainnavigation > #menu > ul');
const menuTitle = document.querySelector('#menu-title');
const menuBurger = document.querySelector('#hamburger');
const menuH2 = document.querySelector('#menu > h2');
menuButton.addEventListener('click', (e) => {
setTimeout(() => {
menuContainer.classList.toggle('open');
menuTitle.classList.toggle('open');
menuBurger.classList.toggle('open');
menuH2.classList.toggle('open');
}, 50);
})
document.addEventListener('click', (e) => {
if (!menuContainer.contains(e.target) && !menuBurger.contains(e.target)) {
menuContainer.classList.remove('open');
menuTitle.classList.remove('open');
menuBurger.classList.remove('open');
menuH2.classList.remove('open');
}
})
}
function setHamburgerWhenLogged() {
if (drupalSettings.user.uid != 0) {
const menuBurger = document.querySelector('#hamburger');
const menuTitle = document.querySelector('#menu-title');
const menuContainer = document.querySelector('#block-caravane-mainnavigation > #menu > ul');
const header = document.querySelector('.dialog-off-canvas-main-canvas');
const headerTop = header.getBoundingClientRect().top;
menuTitle.style.top = `${headerTop}px`;
menuBurger.style.top = `${headerTop}px`;
menuContainer.style.paddingTop = `${headerTop}px`;
}
}
function setupEtapeMapPopup(store, map) {
const icons = document.querySelectorAll('.leaflet-map-divicon');
for (let icon of icons) {
const colorContainer = icon.querySelector('.couleur');
let colorDivs = colorContainer.querySelectorAll('.separated-content');
let color;
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();
const nid = icon.querySelector('.nid');
const nidValue = nid.querySelector('.separated-content').innerText;
icon.addEventListener('click', function(event) {
store.fetchEtapeData(nidValue, map);
});
colorContainer.style.display = "none";
nid.style.display = "none";
const iconElements = icon.querySelectorAll('div');
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];
})
}
}
init()
} // end CaravaneTheme()
CaravaneTheme()
CaravaneTheme()
})(jQuery, Drupal, drupalSettings)

View File

@ -6,6 +6,7 @@ if(drupalDecoupled.redirect){
console.log('window.location', window.location);
drupalDecoupled.sys_path = drupalDecoupled.sys_path.replace(/^\//, '');
drupalDecoupled.url = window.location.pathname;
drupalDecoupled.hash = window.location.hash;
window.localStorage.setItem('decoupled_origin', JSON.stringify(drupalDecoupled));

View File

@ -2,17 +2,23 @@ import { createRouter, createWebHistory } from 'vue-router';
import ModaleView from '../vuejs/Modale.vue';
const routes = [
{
/* {
name: 'etape',
path: '/etapes/:title?',
component: ModaleView,
props: {id: null}
props: {id: null},
},
{
name: 'home',
path: '/',
component: ModaleView
component: ModaleView,
},
*/
// Not much to do here nah ?
{
path: '/:catchAll(.*)',
component: ModaleView,
}
];
const router = createRouter({

View File

@ -5,33 +5,17 @@ import REST from '../api/rest-axios';
export const useContentStore = defineStore('content', {
state: () => ({
href: '',
map: {},
etape: {
title: '',
adresse: {},
contentType: '',
pageTitle: '',
content: {
contentTitle: '',
coordinates: {},
adresse: {},
etape_number: '',
vignette: {},
couleur: '',
previous: {},
dates: {},
previous : {},
next: {},
dates: {
start: {
d: '',
m: '',
y: '',
},
end: {
d: '',
m: '',
y: '',
},
},
parties: [],
},
page: {
title: '',
vignette: {},
parties: [],
},
@ -39,320 +23,237 @@ export const useContentStore = defineStore('content', {
error: null,
}),
actions: {
async fetchEtapeData(nid, map) {
this.resetStore();
this.map = map;
async fetchContentData(path) {
this.resetStore(false);
const contentTypes = [ 'etape', 'static' ];
try {
const response = await REST.get(`/jsonapi/node/etape/`);
for (let etape of response.data.data) {
if (etape.attributes.drupal_internal__nid == nid) {
for (let metatag of etape.attributes.metatag) {
if (metatag.tag === "link") {
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.adresse = etape.attributes.field_adresse;
this.etape.etape_number = etape.attributes.field_arret_numero;
const vignetteFetch = await this.fetchContent('field_vignette', etape.relationships);
if (vignetteFetch) {
this.etape.vignette = {
url: vignetteFetch.attributes.uri.url,
alt: etape.relationships.field_vignette.data.meta.alt
};
}
this.etape.couleur = etape.attributes.field_couleur;
let rawContent,
contentType,
response;
this.etape.dates = {
start: this.getCleanDate(etape.attributes.field_dates.value),
end: this.getCleanDate(etape.attributes.field_dates.end_value),
}
const partiesFetch = await this.fetchContent('field_parties', etape.relationships);
if (partiesFetch) {
this.etape.parties = [];
for (let partie of partiesFetch) {
const partieType = partie.type.replace(/^paragraph--/, "");
let partieContent = {
type: partieType,
};
switch (partieType) {
case 'carte_sensible':
const carteSensibleFetch = await this.fetchContent('field_image_carte', partie.relationships);
if (carteSensibleFetch) {
partieContent.carteSensible = {
url: carteSensibleFetch.attributes.uri.url,
alt: partie.relationships.field_image_carte.data.meta.alt,
};
}
break;
case 'titre_texte':
partieContent.titre = partie.attributes.field_titre;
partieContent.texte = partie.attributes.field_texte.value;
break;
case 'chiffres_cles':
const chiffresClesFetch = await this.fetchContent('field_chiffres_clefs', partie.relationships);
if (chiffresClesFetch) {
partieContent.chiffresCles = [];
for (let chiffre of chiffresClesFetch) {
partieContent.chiffresCles.push({
chiffre: chiffre.attributes.field_chiffre,
description: chiffre.attributes.field_description,
});
}
}
break;
case 'diaporama':
const diaporamaFetch = await this.fetchContent('field_diaporama', partie.relationships);
if (diaporamaFetch) {
partieContent.diaporama = [];
for (let [index, image] of diaporamaFetch.entries()) {
partieContent.diaporama.push({
url: image.attributes.uri.url,
alt: partie.relationships.field_diaporama.data[index].meta.alt,
});
}
}
break;
case 'entretien':
partieContent.entretien = {};
const personnesFetch = await this.fetchContent('field_personne_s', partie.relationships);
const questionsReponsesFetch = await this.fetchContent('field_questions_reponses', partie.relationships);
if (personnesFetch && questionsReponsesFetch) {
partieContent.entretien.personnes = [];
for (let personne of personnesFetch) {
const portraitFetch = await this.fetchContent('field_portrait', personne.relationships);
if (portraitFetch) {
partieContent.entretien.personnes.push({
portrait: portraitFetch.attributes.uri.url,
alt: personne.relationships.field_portrait.data.meta.alt,
description: personne.attributes.field_description,
});
}
}
partieContent.entretien.questionsReponses = [];
for (let qr of questionsReponsesFetch) {
partieContent.entretien.questionsReponses.push({
question: qr.attributes.field_question,
reponse: qr.attributes.field_reponse.value,
});
}
}
break;
case 'exergue':
partieContent.exergue = partie.attributes.field_texte_exergue.value;
break;
case 'video':
partieContent.videos = [];
for (let video of partie.attributes.field_videos) {
const videoId = video.split('?v=')[1];
const videoUrl = `https://www.youtube.com/embed/${videoId}`;
partieContent.videos.push(videoUrl);
}
break;
}
this.etape.parties.push(partieContent);
}
}
// get previous and next étape infos
// the list from the json api /node is not ordered
// and i need authentification to get the json view ordered list
// so i get it from the displayed list in the page
const orderedEtapesList = document.querySelectorAll('#etapes-liste li');
if (orderedEtapesList) {
const processEtape = async (etapeItemNid, etapesList, key) => {
for (let etape of etapesList) {
if (etape.attributes.drupal_internal__nid == etapeItemNid) {
const vignetteFetch = await REST.get(etape.relationships.field_vignette.links.related.href);
this.etape[key] = {
nid: etape.attributes.drupal_internal__nid,
couleur: etape.attributes.field_couleur,
title: etape.attributes.title,
postalCode: etape.attributes.field_adresse.postal_code,
dates: {
start: this.getCleanDate(etape.attributes.field_dates.value),
end: this.getCleanDate(etape.attributes.field_dates.end_value),
},
vignette: {
url: vignetteFetch.data.data.attributes.uri.url,
alt: etape.relationships.field_vignette.data.meta.alt,
},
};
break;
}
}
};
for (let li of orderedEtapesList) {
if (li.querySelector('a').dataset.nodeNid == nid) {
const previousEtapeItemNid = li.previousElementSibling?.querySelector('a').dataset.nodeNid;
const nextEtapeItemNid = li.nextElementSibling?.querySelector('a').dataset.nodeNid;
if (previousEtapeItemNid) {
await processEtape(previousEtapeItemNid, response.data.data, 'previous');
}
if (nextEtapeItemNid) {
await processEtape(nextEtapeItemNid, response.data.data, 'next');
}
}
contentTypesLoop:
for (let type of contentTypes) {
response = await REST.get(`/jsonapi/node/${type}/`);
for (let content of response.data.data) {
for (let tag of content.attributes.metatag) {
if (tag.tag === "link" && tag.attributes.href === path) {
this.contentType = type;
rawContent = content;
contentType = type;
break contentTypesLoop;
}
}
}
}
// pageTitle
for (let tag of rawContent.attributes.metatag) {
if (tag.tag === "meta") {
this.pageTitle = tag.attributes.content;
break;
}
}
this.setActiveItemInMenu(nid);
// contentTitle
this.content.contentTitle = rawContent.attributes.title;
// vignette
const vignetteFetch = await this.fetchDeeperContent('field_vignette', rawContent.relationships);
if (vignetteFetch) {
this.content.vignette = {
url: vignetteFetch.attributes.uri.url,
alt: rawContent.relationships.field_vignette.data.meta.alt
};
}
if (contentType === 'etape') {
// coordinates
this.content.coordinates = {
lat: rawContent.attributes.field_geofield.lat,
lon: rawContent.attributes.field_geofield.lon,
};
// adresse
this.content.adresse = rawContent.attributes.field_adresse;
// étape number
this.content.etape_number = rawContent.attributes.field_arret_numero;
// couleur
this.content.couleur = rawContent.attributes.field_couleur;
// dates
this.content.dates = {
start: this.getCleanDate(rawContent.attributes.field_dates.value),
end: this.getCleanDate(rawContent.attributes.field_dates.end_value),
}
// previous / next
await this.getRelatedEtape('previous', response.data.data, path);
await this.getRelatedEtape('next', response.data.data, path);
}
// parties
const fieldParties = contentType === 'etape' ? 'field_parties' : 'field_parties_static';
const partiesFetch = await this.fetchDeeperContent(fieldParties, rawContent.relationships);
if (partiesFetch) {
this.content.parties = [];
for (let partie of partiesFetch) {
const partieType = partie.type.replace(/^paragraph--/, "");
let partieContent = {
type: partieType,
};
switch (partieType) {
case 'carte_sensible':
const carteSensibleFetch = await this.fetchDeeperContent('field_image_carte', partie.relationships);
if (carteSensibleFetch) {
partieContent.carteSensible = {
url: carteSensibleFetch.attributes.uri.url,
alt: partie.relationships.field_image_carte.data.meta.alt,
};
}
break;
case 'titre_texte':
partieContent.titre = partie.attributes.field_titre;
partieContent.texte = partie.attributes.field_texte.value;
break;
case 'chiffres_cles':
const chiffresClesFetch = await this.fetchDeeperContent('field_chiffres_clefs', partie.relationships);
if (chiffresClesFetch) {
partieContent.chiffresCles = [];
for (let chiffre of chiffresClesFetch) {
partieContent.chiffresCles.push({
chiffre: chiffre.attributes.field_chiffre,
description: chiffre.attributes.field_description,
});
}
}
break;
case 'diaporama':
const diaporamaFetch = await this.fetchDeeperContent('field_diaporama', partie.relationships);
if (diaporamaFetch) {
partieContent.diaporama = [];
for (let [index, image] of diaporamaFetch.entries()) {
partieContent.diaporama.push({
url: image.attributes.uri.url,
alt: partie.relationships.field_diaporama.data[index].meta.alt,
});
}
}
break;
case 'entretien':
partieContent.entretien = {};
const personnesFetch = await this.fetchDeeperContent('field_personne_s', partie.relationships);
const questionsReponsesFetch = await this.fetchDeeperContent('field_questions_reponses', partie.relationships);
if (personnesFetch && questionsReponsesFetch) {
partieContent.entretien.personnes = [];
for (let personne of personnesFetch) {
const portraitFetch = await this.fetchDeeperContent('field_portrait', personne.relationships);
if (portraitFetch) {
partieContent.entretien.personnes.push({
portrait: portraitFetch.attributes.uri.url,
alt: personne.relationships.field_portrait.data.meta.alt,
description: personne.attributes.field_description,
});
}
}
partieContent.entretien.questionsReponses = [];
for (let qr of questionsReponsesFetch) {
partieContent.entretien.questionsReponses.push({
question: qr.attributes.field_question,
reponse: qr.attributes.field_reponse.value,
});
}
}
break;
case 'exergue':
partieContent.exergue = partie.attributes.field_texte_exergue.value;
break;
case 'video':
partieContent.videos = [];
for (let video of partie.attributes.field_videos) {
const videoId = video.split('?v=')[1];
const videoUrl = `https://www.youtube.com/embed/${videoId}`;
partieContent.videos.push(videoUrl);
}
break;
}
this.content.parties.push(partieContent);
}
}
} catch (error) {
this.error = 'Failed to fetch data';
console.error('Issue with getNodeData', error);
} finally {
this.loading = false;
} finally {
this.loading = false;
}
},
async fetchStaticData(nid, map) {
this.resetStore();
this.map = map;
try {
const response = await REST.get(`/jsonapi/node/static/`);
for (let page of response.data.data) {
if (page.attributes.drupal_internal__nid == nid) {
for (let metatag of page.attributes.metatag) {
if (metatag.tag === "link") {
this.href = metatag.attributes.href;
}
}
this.page.title = page.attributes.title;
const vignetteFetch = await this.fetchContent('field_vignette', page.relationships);
if (vignetteFetch) {
this.page.vignette = {
url: vignetteFetch.attributes.uri.url,
alt: page.relationships.field_vignette.data.meta.alt
};
}
const partiesFetch = await this.fetchContent('field_parties_static', page.relationships);
if (partiesFetch) {
this.page.parties = [];
for (let partie of partiesFetch) {
const partieType = partie.type.replace(/^paragraph--/, "");
let partieContent = {
type: partieType,
};
switch (partieType) {
case 'titre_texte':
partieContent.titre = partie.attributes.field_titre;
partieContent.texte = partie.attributes.field_texte.value;
break;
case 'diaporama':
const diaporamaFetch = await this.fetchContent('field_diaporama', partie.relationships);
if (diaporamaFetch) {
partieContent.diaporama = [];
for (let [index, image] of diaporamaFetch.entries()) {
partieContent.diaporama.push({
url: image.attributes.uri.url,
alt: partie.relationships.field_diaporama.data[index].meta.alt,
});
}
}
break;
case 'exergue':
partieContent.exergue = partie.attributes.field_texte_exergue.value;
break;
case 'video':
partieContent.videos = [];
for (let video of partie.attributes.field_videos) {
const videoId = video.split('?v=')[1];
const videoUrl = `https://www.youtube.com/embed/${videoId}`;
partieContent.videos.push(videoUrl);
}
break;
}
this.page.parties.push(partieContent);
getCleanDate(date) {
return {
d: date.split('-')[2],
m: new Intl.DateTimeFormat('fr-FR', { month: 'long' }).format(new Date(date)),
y: date.split('-')[0],
}
},
async getRelatedEtape(direction, allEtapesData, path) {
const getRelatedEtapeContent = async (relatedPath, allEtapesData) => {
const baseUrl = window.location.protocol + "//" + window.location.host;
for (let etape of allEtapesData) {
for (let tag of etape.attributes.metatag) {
if (tag.tag === "link" && tag.attributes.href === baseUrl + relatedPath) {
const vignetteFetch = await REST.get(etape.relationships.field_vignette.links.related.href);
this.content[direction] = {
url: tag.attributes.href,
couleur: etape.attributes.field_couleur,
title: etape.attributes.title,
postalCode: etape.attributes.field_adresse.postal_code,
dates: {
start: this.getCleanDate(etape.attributes.field_dates.value),
end: this.getCleanDate(etape.attributes.field_dates.end_value),
},
vignette: {
url: vignetteFetch.data.data.attributes.uri.url,
alt: etape.relationships.field_vignette.data.meta.alt,
},
}
}
}
}
this.setActiveItemInMenu(nid);
} catch (error) {
}
const orderedEtapesList = document.querySelectorAll('#etapes-liste li');
if (orderedEtapesList) {
for (let li of orderedEtapesList) {
const liHref = li.querySelector('a').getAttribute('href');
if (path.endsWith(liHref)) {
const previousEtapeItemPath = li.previousElementSibling?.querySelector('a').getAttribute('href');
const nextEtapeItemPath = li.nextElementSibling?.querySelector('a').getAttribute('href');
if (previousEtapeItemPath && direction === 'previous') {
let prevContent = await getRelatedEtapeContent(previousEtapeItemPath, allEtapesData);
return prevContent;
}
if (nextEtapeItemPath && direction === 'next') {
let nextContent = await getRelatedEtapeContent(nextEtapeItemPath, allEtapesData);
return nextContent;
}
}
}
}
},
async fetchDeeperContent(field, relationships) {
if (relationships[field].data) {
try {
const contentLink = relationships[field].links.related.href;
const contentFetch = await REST.get(contentLink);
return contentFetch.data.data;
} catch (error) {
this.error = 'Failed to fetch data';
console.error('Issue with getNodeData', error);
} finally {
this.loading = false;
}
},
async fetchContent(field, relationships) {
if (relationships[field].data) {
try {
const contentLink = relationships[field].links.related.href;
const contentFetch = await REST.get(contentLink);
return contentFetch.data.data;
} catch (error) {
this.error = 'Failed to fetch data';
console.error('Issue with getNodeData', error);
}
}
},
emptyAll(nid, map) {
this.href = '';
this.map = map;
this.etape = {};
this.page = {};
this.setActiveItemInMenu(nid);
},
setActiveItemInMenu(nid) {
const title = this.etape.title || this.page.title;
const generalLinks = document.querySelectorAll('#menu > ul > li > a');
if (Object.entries(this.etape).length === 0 && Object.entries(this.page).length === 0) {
for (let link of generalLinks) {
link.classList.remove('is-active');
}
generalLinks[0].classList.add('is-active');
} else {
for (let link of generalLinks) {
if (link.dataset.nodeNid == nid) {
link.classList.add('is-active');
} else {
link.classList.remove('is-active');
}
}
}
const etapeLinks = document.querySelectorAll('#etapes-liste li');
for (let link of etapeLinks) {
const a = link.querySelector('a');
if (a.innerText === title) {
link.classList.remove('inactive');
} else {
link.classList.add('inactive');
}
}
const inactiveLinks = document.querySelectorAll('#etapes-liste li.inactive');
if (inactiveLinks.length === etapeLinks.length) {
for (let link of inactiveLinks) {
link.classList.remove('inactive');
}
}
},
resetStore() {
this.loading = true;
resetStore(forFrontDisplay) {
this.contentType = '';
this.pageTitle = '';
this.content = {};
this.loading = !forFrontDisplay;
this.error = null;
this.etape = {};
this.page = {};
},
getCleanDate(date) {
return {
d: date.split('-')[2],
m: new Intl.DateTimeFormat('fr-FR', { month: 'long' }).format(new Date(date)),
y: date.split('-')[0],
}
},
}
},
});

View File

@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
export const useMapStore = defineStore('mapState', {
state: () => ({
map: Object,
defaultZoom: Number,
defaultMapCenter: Object,
currentPlace: Object,
@ -10,36 +11,36 @@ export const useMapStore = defineStore('mapState', {
duration: 3,
}),
actions: {
zoomToPlace(map, lat, long) {
map.flyTo([lat, long], this.maxZoom, { duration: this.duration });
this.currentZoom = this.maxZoom;
zoomToPlace(lat, long) {
this.map.flyTo([lat, long], this.maxZoom, { duration: this.duration });
this.currentZoom = this.maxZoom;
},
resetMap(map) {
map.flyTo(this.defaultMapCenter, this.defaultZoom, { duration: this.duration });
resetMap() {
this.map.flyTo(this.defaultMapCenter, this.defaultZoom, { duration: this.duration });
this.currentZoom = this.defaultZoom;
},
lockMap(map) {
lockMap() {
setTimeout(() => {
map.options.minZoom = this.currentZoom;
map.options.maxZoom = this.currentZoom;
this.map.options.minZoom = this.currentZoom;
this.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();
this.map.dragging.disable();
this.map.touchZoom.disable();
this.map.doubleClickZoom.disable();
this.map.scrollWheelZoom.disable();
this.map.boxZoom.disable();
this.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();
unlockMap() {
this.map.options.minZoom = this.defaultZoom;
this.map.options.maxZoom = this.maxZoom;
this.map.dragging.enable();
this.map.touchZoom.enable();
this.map.doubleClickZoom.enable();
this.map.scrollWheelZoom.enable();
this.map.boxZoom.enable();
this.map.keyboard.enable();
// map.tap.enable();
},
},

View File

@ -0,0 +1,37 @@
import { setActiveNavItem } from "./set-active-nav-item";
export async function initFirstLoadRouting(store, router, baseUrl, siteName) {
const decoupled_origin = JSON.parse(window.localStorage.getItem('decoupled_origin'));
if(decoupled_origin) {
await store.fetchContentData(baseUrl + decoupled_origin.url);
router.push(decoupled_origin.url);
window.localStorage.removeItem("decoupled_origin");
document.title = store.pageTitle;
setActiveNavItem(store.contentType, decoupled_origin.url);
} else {
document.title = siteName;
}
}
export function handleClickableElements(clickableElements, store, router, baseUrl, siteName, mapStore) {
for (const link of clickableElements) {
let href = link.href || link.dataset.href;
if (href.startsWith(baseUrl)) href = href.replace(baseUrl, '');
link.onclick = async function (e) {
router.push(href);
if (href !== window.location.pathname) {
if (href === '/') {
store.resetStore(true);
document.title = siteName;
mapStore.resetMap();
} else {
await store.fetchContentData(baseUrl + href);
document.title = store.pageTitle;
}
setActiveNavItem(store.contentType, href);
}
}
}
}

View File

@ -0,0 +1,37 @@
export function setMenuToggle() {
const menuButton = document.querySelector('#block-caravane-mainnavigation > #menu');
const menuContainer = document.querySelector('#block-caravane-mainnavigation > #menu > ul');
const menuTitle = document.querySelector('#menu-title');
const menuBurger = document.querySelector('#hamburger');
const menuH2 = document.querySelector('#menu > h2');
menuButton.addEventListener('click', (e) => {
setTimeout(() => {
menuContainer.classList.toggle('open');
menuTitle.classList.toggle('open');
menuBurger.classList.toggle('open');
menuH2.classList.toggle('open');
}, 50);
});
document.addEventListener('click', (e) => {
if (!menuContainer.contains(e.target) && !menuBurger.contains(e.target)) {
menuContainer.classList.remove('open');
menuTitle.classList.remove('open');
menuBurger.classList.remove('open');
menuH2.classList.remove('open');
}
});
}
export function setHamburgerWhenLogged(drupalSettings) {
if (drupalSettings.user.uid != 0) {
const menuBurger = document.querySelector('#hamburger');
const menuTitle = document.querySelector('#menu-title');
const menuContainer = document.querySelector('#block-caravane-mainnavigation > #menu > ul');
const header = document.querySelector('.dialog-off-canvas-main-canvas');
const headerTop = header.getBoundingClientRect().top;
menuTitle.style.top = `${headerTop}px`;
menuBurger.style.top = `${headerTop}px`;
menuContainer.style.paddingTop = `${headerTop}px`;
}
}

View File

@ -0,0 +1,6 @@
export function setupMapStore(mapStore, map, settings) {
mapStore.map = map;
mapStore.defaultMapCenter = map.getCenter();
mapStore.maxZoom = settings.settings.maxZoom;
mapStore.defaultZoom = settings.settings.minZoom;
}

View File

@ -0,0 +1,80 @@
export function processClickableElements() {
return {
etapeListLinks: processEtapeLinks(),
generalListLinks: processStaticLinks(),
logoLink: processLogoLink(),
mapIcons: processMapIcons(),
};
}
function processEtapeLinks() {
const etape_li = document.querySelectorAll('#etapes-liste li');
etape_li.forEach((li) => {
const etape_link = li.querySelector('a.etape-link');
etape_link.addEventListener('click', (e) => e.preventDefault());
const couleur = etape_link.dataset.couleur;
li.dataset.href = etape_link.attributes.href.value;
const iconElements = li.querySelectorAll('.icone-arret > div');
for (let element of iconElements) {
element.style.backgroundColor = couleur;
}
});
return etape_li;
}
function processStaticLinks() {
const general_link_fields = document.querySelectorAll('#menu > ul > li > a');
for (let i = 0; i < general_link_fields.length; i ++) {
let general_link_path = general_link_fields[i].getAttribute('data-drupal-link-system-path');
if (general_link_path && general_link_path !== '<front>') {
const match = [...general_link_path.match(/^node\/(\d+)$/)];
if (match) {
const nid = match[1];
general_link_fields[i].setAttribute('data-nid', parseInt(nid));
}
}
general_link_fields[i].addEventListener('click', (e) => e.preventDefault());
}
return general_link_fields;
}
function processLogoLink() {
const logo = document.querySelector('#block-caravane-logocaravane a');
logo.addEventListener('click', (e) => e.preventDefault());
return logo;
}
function processMapIcons() {
const icons = document.querySelectorAll('.leaflet-map-divicon');
for (let icon of icons) {
icon.setAttribute('title', '');
const hrefContainer = icon.querySelector('.url');
icon.dataset.href = hrefContainer.innerText;
hrefContainer.style.display = "none";
const colorContainer = icon.querySelector('.couleur');
let color = colorContainer.innerText;
colorContainer.style.display = "none";
const iconElements = icon.querySelectorAll('div');
for (let iconElement of iconElements) {
iconElement.style.backgroundColor = color;
}
icon.addEventListener('mouseenter', () => {
icon.style.transform = `${icon.style.transform} scale(1.1)`;
const popup = document.querySelector('.leaflet-tooltip-center > div');
popup.style.opacity = "1";
});
icon.addEventListener('mouseleave', () => {
icon.style.transform = icon.style.transform.split(' ')[0] + icon.style.transform.split(' ')[1] + icon.style.transform.split(' ')[2];
});
}
return icons;
}

View File

@ -0,0 +1,35 @@
export function setActiveNavItem(contentType, href) {
const staticNavItems = document.querySelectorAll('#menu > ul > li > a');
const etapeNavItems = document.querySelectorAll('#etapes-liste li a');
for (let item of staticNavItems) {
item.classList.remove('is-active');
}
for (let item of etapeNavItems) {
item.closest('li').classList.add('inactive');
}
if (href === '/' || href === '') {
staticNavItems[0].classList.add('is-active');
for (let item of etapeNavItems) {
item.closest('li').classList.remove('inactive');
}
} else {
if (contentType === 'static') {
for (let item of staticNavItems) {
if (item.getAttribute('href') === href) {
item.classList.add('is-active');
}
}
} else if (contentType === 'etape') {
for (let item of etapeNavItems) {
if (item.getAttribute('href') === href) {
item.closest('li').classList.remove('inactive');
}
}
}
}
}

View File

@ -0,0 +1,22 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from '../router/router';
import Modale from '../vuejs/Modale.vue';
import VueImageZoomer from 'vue-image-zoomer';
import 'vue-image-zoomer/dist/style.css';
import { useContentStore } from '../stores/content';
import { useMapStore } from '../stores/mapState';
export function initVueContentModale() {
const app = createApp(Modale)
.use(createPinia())
.use(router)
.use(VueImageZoomer);
const store = useContentStore();
const mapStore = useMapStore();
app.mount('#content-modale');
return { store, mapStore, router };
}

View File

@ -1,44 +1,46 @@
<template>
<Transition>
<div v-if="isEtapeValid || isPageValid">
<div v-if="!loading && (contentType === 'etape' || contentType === 'static')">
<div class="content-wrapper">
<ModaleHeader
:content="etape.title ? etape : page"
:couleur="etape.couleur || brandColor" />
<ModaleHeader
:contentType="contentType"
:content="content"
:couleur="content.couleur || brandColor" />
<main>
<div v-for="partie in etape.parties || page.parties" class="partie">
<div v-for="partie in content.parties" class="partie">
<ModaleCarteSensible
v-if="partie.type === 'carte_sensible'"
:partie="partie" />
<ModaleTitreTexte
v-if="partie.type === 'titre_texte'"
:partie="partie"
:couleur="etape.couleur || brandColor" />
:couleur="content.couleur || brandColor" />
<ModaleChiffresCles
v-if="partie.type === 'chiffres_cles'"
:partie="partie"
:couleur="etape.couleur || brandColor" />
:couleur="content.couleur || brandColor" />
<ModaleDiaporama
v-if="partie.type === 'diaporama'"
:partie="partie"
:couleur="etape.couleur || brandColor" />
:couleur="content.couleur || brandColor" />
<ModaleEntretien
v-if="partie.type === 'entretien'"
:partie="partie"
:couleur="etape.couleur || brandColor" />
:couleur="content.couleur || brandColor" />
<ModaleExergue
v-if="partie.type === 'exergue'"
:partie="partie"
:couleur="etape.couleur || brandColor" />
:couleur="content.couleur || brandColor" />
<ModaleVideos
v-if="partie.type === 'video'"
:partie="partie" />
</div>
</main>
<ModaleFooter
:content="etape || page"
:couleur="etape.couleur || brandColor"
:map="map" />
:contentType="contentType"
:content="content"
:couleur="content.couleur || brandColor"
/>
</div>
</div>
</Transition>
@ -46,10 +48,10 @@
<script setup>
import { computed, watch, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useContentStore } from '../stores/content';
import { useMapStore } from '../stores/mapState';
import { useRoute, useRouter } from 'vue-router';
import ModaleHeader from './components/ModaleHeader.vue';
import ModaleFooter from './components/ModaleFooter.vue';
@ -62,132 +64,95 @@ import ModaleEntretien from './components/parties/ModaleEntretien.vue';
import ModaleExergue from './components/parties/ModaleExergue.vue';
import ModaleVideos from './components/parties/ModaleVideos.vue';
import { useUtils } from './composables/useUtils';
const { isObjectEmpty, scrollTop } = useUtils();
const router = useRouter();
const store = useContentStore();
const mapState = useMapStore();
const route = useRoute();
const { loading, error, href, map, etape, page } = storeToRefs(store);
const { duration } = storeToRefs(mapState);
const {
contentType,
content,
loading,
error,
} = storeToRefs(store);
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 { map, duration } = storeToRefs(mapState);
let isModaleEtape, wasModaleEtape;
const brandColor = "#80c8bf";
let isProgrammaticNavigation = false;
const handleRouteChange = () => {
watch(
() => route.params.id,
(newId) => {
if (isProgrammaticNavigation) {
isProgrammaticNavigation = false;
return;
}
if (!newId) {
store.emptyAll(map.value);
} else {
store.fetchEtapeData(newId, map.value);
if (!etape.value?.data) {
store.fetchStaticData(newId, map.value);
}
scrollTop();
}
},
{ immediate: true }
);
};
const handleColorChange = () => {
watch(
() => href.value,
() => {
document.documentElement.style.setProperty('--etape-couleur', etape.value.couleur || brandColor);
}
);
};
const handleHrefChange = () => {
watch(
() => href.value,
(newHref) => {
const relativePath = newHref.split('.fr')[1];
isProgrammaticNavigation = true;
if (newHref == '') {
router.push('/');
mapState.unlockMap(map.value)
} else {
if (relativePath && relativePath !== '' && relativePath !== '/') {
mapState.lockMap(map.value);
router.push(relativePath);
scrollTop();
}
}
() => content.value.couleur,
() => {
if (contentType.value === 'etape' && content.value.couleur) {
document.documentElement.style.setProperty('--etape-couleur', content.value.couleur || brandColor);
}
}
);
};
const handleMapMovement = () => {
watch(
() => href.value,
() => loading.value,
() => {
isModaleEtape = !isObjectEmpty(etape.value);
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;
if (!loading.value) {
isModaleEtape = contentType.value === 'etape';
if (!wasModaleEtape && isModaleEtape) {
// national -> détail
document.documentElement.style.setProperty('--modale-enter-delay', `${duration.value}s`);
mapState.zoomToPlace(content.value.coordinates.lat, content.value.coordinates.lon);
} else if (wasModaleEtape && isModaleEtape) {
// détail -> détail
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(content.value.coordinates.lat, content.value.coordinates.lon);
}, duration.value * 1000);
} else if (wasModaleEtape && !isModaleEtape) {
// détail -> national
document.documentElement.style.setProperty('--modale-leave-delay', 0);
document.documentElement.style.setProperty('--modale-enter-delay', `${duration.value}s`);
mapState.resetMap();
} else if (!wasModaleEtape && !isModaleEtape) {
// national -> national
console.log('national -> national');
document.documentElement.style.setProperty('--modale-leave-delay', 0);
document.documentElement.style.setProperty('--modale-enter-delay', '0.5s');
}
wasModaleEtape = isModaleEtape;
}
},
);
};
onMounted(() => {
isModaleEtape = !isObjectEmpty(etape.value);
wasModaleEtape = isModaleEtape;
handleRouteChange();
handleColorChange();
handleHrefChange();
handleMapMovement();
isModaleEtape = contentType.value === 'etape';
wasModaleEtape = isModaleEtape;
handleColorChange();
handleMapMovement();
});
</script>
<style scss>
<style scoped scss>
.v-enter-active {
transition: all 0.5s linear var(--modale-enter-delay);
transition: margin-top 0.5s ease-out var(--modale-enter-delay);
}
.v-leave-active {
transition: all 0.5s linear var(--modale-leave-delay);
transition: margin-top 0.5s ease-in var(--modale-leave-delay);
}
.v-enter-from,
.v-leave-to {
transform: translateY(20vh);
margin-top: 150vh;
}
.v-enter-to,
.v-leave-from {
transform: translateY(0vh);
margin-top: 0vh;
}
</style>

View File

@ -4,8 +4,8 @@
<div class="pattern"></div>
</div>
<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, map)">
<div v-if="contentType === 'etape' && (content.previous || content.next)" class="related-etape-links">
<div v-if="content.previous" class="card previous" @click="displayRelatedElement(content.previous.url)">
<div class="icon">
<div :style="{ backgroundColor: content.previous.couleur }"></div>
<div :style="{ backgroundColor: content.previous.couleur }"></div>
@ -21,7 +21,7 @@
</div>
</div>
</div>
<div v-if="content.next" class="card next" @click="store.fetchEtapeData(content.next.nid, map)">
<div v-if="content.next" class="card next" @click="displayRelatedElement(content.next.url)">
<div class="icon">
<div :style="{ backgroundColor: content.next.couleur }"></div>
<div :style="{ backgroundColor: content.next.couleur }"></div>
@ -42,14 +42,25 @@
</template>
<script setup>
import router from '../../router/router.js';
import { useContentStore } from '../../stores/content';
const brandColor = "#80c8bf";
const store = useContentStore();
const props = defineProps({
contentType: String,
content: Object,
couleur: String,
map: Object,
});
async function displayRelatedElement(href) {
const baseUrl = window.location.protocol + "//" + window.location.host;
if (href.startsWith(baseUrl)) href = href.replace(baseUrl, '');
router.push(href);
await store.fetchContentData(baseUrl + href);
document.title = store.pageTitle;
}
</script>

View File

@ -1,9 +1,9 @@
<template>
<header :style="content.vignette ? '' : { marginTop: '20vh' }">
<div class="cover">
<img v-if="content.vignette" :src="content.vignette.url" :alt="content.vignette.alt">
<img v-if="content.vignette" :src="content.vignette.url" :alt="content.vignette.alt">
</div>
<div v-if="content.dates" class="cartouche" :style="{ backgroundColor: couleur }">
<div v-if="contentType === 'etape' && content.dates" class="cartouche" :style="{ backgroundColor: couleur }">
<p>Étape n°{{content.etape_number}}</p>
<p>Du {{content.dates.start.d}} {{content.dates.start.m}} au {{ content.dates.end.d }} {{ content.dates.end.m }} {{ content.dates.end.y }}</p>
</div>
@ -13,7 +13,7 @@
<div class="locality">
<div class="top-triangle"></div>
<div class="locality-title">
<h1>{{content.title}} <em v-if="content.adresse">({{ content.adresse.postal_code.slice(0, 2) }})</em></h1>
<h1>{{content.contentTitle}} <em v-if="content.adresse">({{ content.adresse.postal_code.slice(0, 2) }})</em></h1>
</div>
</div>
</header>
@ -21,6 +21,7 @@
<script setup>
const props = defineProps({
contentType: String,
content: Object,
couleur: String,
});

View File

@ -1,21 +0,0 @@
export function useUtils() {
const isObjectEmpty = (obj) => {
if (!obj || typeof obj !== 'object') return true;
return !Object.keys(obj).some((key) => {
const value = obj[key];
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object') return !isObjectEmpty(value);
return value !== null && value !== undefined && value !== '';
});
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return {
isObjectEmpty,
scrollTop,
};
}

View File

@ -253,6 +253,9 @@ body{
top: 0;
width: 100vw;
.leaflet-container {
.leaflet-popup {
display: none;
}
// add map style here
.leaflet-control-zoom {
border: none;

View File

@ -27,7 +27,8 @@ function caravane_preprocess_html(&$variables) {
* Implements hook_preprocess_HOOK() for page.html.twig.
*/
function caravane_preprocess_page(&$variables) {
$config = \Drupal::config('system.site');
$variables['site_name'] = $config->get('name');
}
/**

View File

@ -89,3 +89,4 @@
{% endif %}
</div>{# /.layout-container #}
<div id="site_name" style="display: none;">{{ site_name }}</div>