2026.camp.carte/js/index.js

235 lines
No EOL
6.7 KiB
JavaScript

import * as MAP from "./map.js"
import { PlaceDatabase } from "./places.js";
import {FeatureElement} from "./components/feature.js"
import "./components/feature-short-header.js"
/** La carte */
/** La base de données de lieux @type {PlaceDatabase|null} */
let places = null
// CHARGEMENT
await Promise.all([
// Base de données des zones
PlaceDatabase.createDefault().then(db => places = db),
MAP.init()
]);
// Un petit point de debug
window.interhackPlaces = places;
await MAP.init_places(places)
// GESTION de la recherche
const search_form = document.getElementById("search-section")
search_form.elements["query"].addEventListener("change", () => search_form.requestSubmit())
search_form.elements["query"].addEventListener("input", () => search_form.requestSubmit())
function showDefaultMapState(){
MAP.unhighlight_all()
for(let feature of places.featuresShownOnEmptyMap){
MAP.show(feature)
}
}
window.addEventListener("hashchange", () => {
let place_id = decodeURIComponent(location.hash.substring(1))
if (place_id) {
let feature = places.getFeatureById(place_id);
if (feature) {
MAP.highlight(place_id)
if(document.getElementById("search-result")) {
// If we have a search in progress, opening search item
openSearchResultItem(feature)
} else {
let foundIndex = null;
let panelChildren = document.getElementById("result-panel").children
for(let i = 0; i<panelChildren.length; i++) {
let feature_el = panelChildren[i];
if(feature_el instanceof FeatureElement && feature_el.feature.id == feature.id){
foundIndex = i;
break;
}
}
if(foundIndex != null){
// If a panel with this feature was already added to dom, scrolling to this item
document.getElementById("result-panel").setActiveChildrenIndex(foundIndex, {behavior: "instant"})
} else {
// If nothing was selected and there is no search result, opening feature only
openFeature(feature)
}
}
if(document.getElementById("result-panel").children.length > 1){
for(let feature_el of document.getElementById("result-panel").children) {
if(feature_el instanceof FeatureElement){
MAP.show(feature_el.feature)
}
}
} else {
for(let feature of places.featuresShownOnEmptyMap){
MAP.show(feature)
}
}
}
} else {
document.getElementById("result-panel").replaceChildren(null)
showDefaultMapState()
}
})
window.dispatchEvent(new Event("hashchange"))
search_form.addEventListener("submit", e => {
e.preventDefault()
if(places){
let data = new FormData(search_form)
if(data.get("query")){
let resultElements = []
let search_results = places.search(data.get("query"))
MAP.unhighlight_all();
for(let result_item of search_results){
let el = document.createElement("li")
let a = document.createElement("a")
el.append(a)
a.href = "#"+encodeURIComponent(result_item.ref)
let header = document.createElement("camp-feature-short-header")
header.feature = result_item.feature
a.append(header)
resultElements.push(el)
MAP.show(result_item.feature)
}
if(!document.getElementById("search-result")){
let el = document.createElement("ol")
el.id = "search-result"
document.getElementById("result-panel").replaceChildren(el)
}
if(resultElements.length > 0){
document.getElementById("search-result").replaceChildren(...resultElements)
} else {
document.getElementById("search-result").replaceChildren(document.createTextNode("Pas de resultat"))
}
} else {
document.getElementById("search-result")?.remove()
showDefaultMapState()
}
}
})
function openSearchResultItem(feature){
let searchResultContainer = document.getElementById("search-result")
if(searchResultContainer){
let featureIndex = -1
let featureFound = false
let newChildren = []
for(let el of searchResultContainer.children){
let featureHeader = el.querySelector("camp-feature-short-header")
if(!featureHeader)
continue
let root = document.createElement("camp-feature")
root.feature = featureHeader.feature
newChildren.push(root)
if(!featureFound){
featureIndex += 1
if(feature == root.feature){
featureFound = true
}
}
}
let panel = document.getElementById("result-panel")
panel.replaceChildren(...newChildren)
requestAnimationFrame(() => {
if(featureFound){
panel.setActiveChildrenIndex(featureIndex, {behavior: "instant"})
} else {
panel.activeChildrenIndex = 0
}
})
updateActiveFeature(feature)
}
}
function openFeature(feature){
let panel = document.getElementById("result-panel")
let root = document.createElement("camp-feature")
root.feature = feature
panel.replaceChildren(root)
requestAnimationFrame(() => {
panel.setActiveChildrenIndex(0, {behavior: "instant"})
})
updateActiveFeature(feature)
}
function updateActiveFeature(feature_or_featureid){
let newUrl = new URL(window.location)
if(feature_or_featureid){
newUrl.hash = encodeURIComponent(feature_or_featureid.id || feature_or_featureid)
} else {
newUrl.hash = "";
}
if(newUrl.toString() != location.toString()){
window.history.replaceState(null, "", newUrl.toString())
window.dispatchEvent(new Event("hashchange"))
}
}
document.getElementById("result-panel").addEventListener("activePanelChange", e => {
console.log("Panel changed", e.activePanelIndex)
if(e.activePanelIndex || e.activePanelIndex === 0){
let activeElement = e.target.children[e.activePanelIndex]
if(activeElement instanceof FeatureElement){
let feature = activeElement.feature
updateActiveFeature(feature)
} else {
updateActiveFeature(null)
}
}
})
{
const RESULT_PANEL = document.getElementById("result-panel")
/** @type {HTMLDialogElement} */
const RESULT_DIALOG = document.getElementById("result-panel-dialog")
function udpateDialogState(){
if(RESULT_PANEL.children.length > 0) {
// BUG d'accessibilité: Il ne faut pas modifier le focus lors de l'ouverture d'une modale
// Il est preferable de ne pas ouvrir la modale avant d'être sur que l'utilisateur·ice veux vraiment y aller
let active_element = document.activeElement;
RESULT_DIALOG.show()
if(active_element){
active_element.focus()
}
} else {
RESULT_DIALOG.close()
}
}
// Oberve changes in modal DOM
new MutationObserver(udpateDialogState).observe(RESULT_PANEL, {
childList: true
})
// Empty result panel on close
document.getElementById("close-result-panel-btn").addEventListener("click", e => {
updateActiveFeature(null)
document.getElementById("result-panel").replaceChildren()
})
//Init
udpateDialogState()
}