Ajout d'un panneau vertical pour le resultat de la recerche
This commit is contained in:
parent
f7e5bb37bf
commit
7d45e2f717
8 changed files with 549 additions and 18 deletions
264
js/components/bidi-panel.js
Normal file
264
js/components/bidi-panel.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
const TEMPLATE = document.createElement("template");
|
||||
TEMPLATE.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
padding: 0.5ex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host(.single-panel) #navigation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navigation .progress-container {
|
||||
flex: 1;
|
||||
flex-shrink: 100;
|
||||
}
|
||||
|
||||
#navigation button {
|
||||
flex-shrink: 0;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
background: none;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#navigation button img {
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-container progress {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#progress-dots {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
gap: 0.5ex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#progress-dots .dot {
|
||||
width: 0.75ex;
|
||||
height: 0.75ex;
|
||||
border-radius: 100%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
#progress-dots .dot.active {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: inherit;
|
||||
}
|
||||
|
||||
#content ::slotted(*) {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
scroll-snap-align: start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section id="content"><slot></slot></section>
|
||||
|
||||
<nav id="navigation" part="navigation-panel">
|
||||
<button id="nav-left-panel" title="Aller au panneau de gauche" part="nav-button-left">
|
||||
<img alt="Fleche vers la gauche" src="${new URL("../../icons/arrow-left.svg", import.meta.url)}"/>
|
||||
</button>
|
||||
<div class="progress-container">
|
||||
<progress id="panel-progress-bar" value="0"></progress>
|
||||
<div id="progress-dots" aria-hidden="true"></div>
|
||||
</div>
|
||||
<button id="nav-right-panel" title="Aller au panneau de droite" part="nav-button-right" >
|
||||
<img alt="Fleche vers la droite" src="${new URL("../../icons/arrow-right.svg", import.meta.url)}"/>
|
||||
</button>
|
||||
</nav>
|
||||
`
|
||||
|
||||
export class BidiPanelElement extends HTMLElement {
|
||||
|
||||
shadow
|
||||
|
||||
mutationObserver
|
||||
|
||||
#lastActive
|
||||
#currentIndex
|
||||
|
||||
get contentContainer(){
|
||||
return this.shadow.getElementById("content")
|
||||
}
|
||||
|
||||
get progressBar(){
|
||||
return this.shadow.getElementById("panel-progress-bar")
|
||||
}
|
||||
|
||||
get progressDotsContainer(){
|
||||
return this.shadow.getElementById("progress-dots")
|
||||
}
|
||||
|
||||
get activeChildrenIndex(){
|
||||
let thisGeometry = this.getBoundingClientRect()
|
||||
let progress = this.contentContainer.scrollLeft / (this.contentContainer.scrollWidth - thisGeometry.width)
|
||||
let focusedElementIndex = Math.floor(progress * this.children.length)
|
||||
if(Number.isFinite(focusedElementIndex)){
|
||||
return focusedElementIndex
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
set activeChildrenIndex(newIndex){
|
||||
this.setActiveChildrenIndex(newIndex, {behavior: "auto"})
|
||||
}
|
||||
|
||||
setActiveChildrenIndex(newIndex, options){
|
||||
let newPanel = this.children[newIndex]
|
||||
let newPanelRect = newPanel.getBoundingClientRect()
|
||||
let selfRect = this.getBoundingClientRect()
|
||||
this.contentContainer.scrollTo({
|
||||
left: this.contentContainer.scrollLeft + ( newPanelRect.left - selfRect.left ),
|
||||
behavior: options?.behavior || "auto"
|
||||
})
|
||||
this.#currentIndex = newIndex
|
||||
}
|
||||
|
||||
get activeChildren(){
|
||||
return this.children[this.activeChildrenIndex]
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
if(!this.shadow){
|
||||
this.shadow = this.attachShadow({ mode: "open" })
|
||||
this.shadow.append(TEMPLATE.content.cloneNode(true))
|
||||
}
|
||||
this.contentContainer.addEventListener("scroll", this.handleContentScroll.bind(this))
|
||||
this.shadow.getElementById("nav-left-panel").addEventListener("click", () => this.previous());
|
||||
this.shadow.getElementById("nav-right-panel").addEventListener("click", () => this.next());
|
||||
|
||||
this.mutationObserver = (new MutationObserver(this.handleMutations.bind(this)))
|
||||
this.mutationObserver.observe(this, {childList:true})
|
||||
|
||||
this.updateProgress()
|
||||
}
|
||||
|
||||
handleMutations(mutation){
|
||||
let newIndex = this.#currentIndex
|
||||
this.updateProgress()
|
||||
if(Number.isFinite(newIndex)){
|
||||
this.setActiveChildrenIndex(newIndex, {behavior: "instant"})
|
||||
}
|
||||
}
|
||||
|
||||
handleContentScroll(e){
|
||||
this.updateProgress()
|
||||
this.#currentIndex = this.activeChildrenIndex
|
||||
if(this.#currentIndex != this.#lastActive){
|
||||
this.dispatchEvent(new ActivePanelChangeEvent("activePanelChange", this.#currentIndex))
|
||||
this.#lastActive = this.#currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(){
|
||||
let focusedElementIndex = this.activeChildrenIndex;
|
||||
|
||||
let progressBar = this.progressBar;
|
||||
progressBar.value = focusedElementIndex;
|
||||
progressBar.min = 0;
|
||||
progressBar.max = this.children.length-1;
|
||||
|
||||
this.classList.toggle("single-panel", this.children.length == 1)
|
||||
this.classList.toggle("empty", this.children.length == 0)
|
||||
|
||||
let dotsContainer = this.progressDotsContainer;
|
||||
if(dotsContainer.children.length != parseInt(progressBar.max)+1){
|
||||
let dots = (new Array(this.children.length))
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
let el = document.createElement("div")
|
||||
el.classList.add("dot")
|
||||
el.part = "dot"
|
||||
return el
|
||||
});
|
||||
dotsContainer.replaceChildren(...dots)
|
||||
}
|
||||
for(let inactiveDot of dotsContainer.querySelectorAll(".active")){
|
||||
inactiveDot.part = "dot"
|
||||
inactiveDot.classList.remove("active")
|
||||
}
|
||||
|
||||
let activeDot = dotsContainer.children[parseInt(progressBar.value)]
|
||||
if(activeDot){
|
||||
activeDot.part = "dot-active"
|
||||
activeDot.classList.add("active")
|
||||
}
|
||||
}
|
||||
|
||||
next(){
|
||||
let nextItem = this.activeChildrenIndex + 1;
|
||||
if(nextItem < this.children.length){
|
||||
this.activeChildrenIndex = nextItem
|
||||
}
|
||||
}
|
||||
|
||||
previous(){
|
||||
let nextItem = this.activeChildrenIndex - 1;
|
||||
if(nextItem >= 0){
|
||||
this.activeChildrenIndex = nextItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivePanelChangeEvent extends Event {
|
||||
constructor(name, activePanelIndex){
|
||||
super(name)
|
||||
this.activePanelIndex = activePanelIndex
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("camp-bidi-panel", BidiPanelElement);
|
||||
43
js/components/feature-short-header.js
Normal file
43
js/components/feature-short-header.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
const TEMPLATE = document.createElement("template")
|
||||
TEMPLATE.innerHTML = `
|
||||
<h2 class="feature-name"></h2>
|
||||
<small class="feature-location"></small>
|
||||
`
|
||||
|
||||
export class FeatureShortHeaderElement extends HTMLElement {
|
||||
|
||||
#feature
|
||||
|
||||
get feature(){
|
||||
return this.#feature
|
||||
}
|
||||
|
||||
set feature(value){
|
||||
this.#feature = value
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.updateContent()
|
||||
}
|
||||
|
||||
updateContent(){
|
||||
if(!this.feature){
|
||||
this.replaceChildren()
|
||||
} else {
|
||||
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
||||
this.querySelector(".feature-name").textContent =
|
||||
this.feature?.properties?.name ||
|
||||
this.feature.mapSymbol.genericName ||
|
||||
this.feature.id
|
||||
|
||||
let featurePoint = this.feature.asPoint()
|
||||
|
||||
this.querySelector(".feature-location").textContent =
|
||||
this.feature?.properties?.["location-description"] ||
|
||||
`${featurePoint.geometry.coordinates[1]}, ${featurePoint.geometry.coordinates[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define("camp-feature-short-header", FeatureShortHeaderElement)
|
||||
26
js/components/feature.js
Normal file
26
js/components/feature.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import "./feature-short-header.js"
|
||||
|
||||
const TEMPLATE = document.createElement("template")
|
||||
TEMPLATE.innerHTML = `
|
||||
<camp-feature-short-header class="feature-header" ></camp-feature-short-header>
|
||||
`
|
||||
|
||||
export class FeatureElement extends HTMLElement {
|
||||
|
||||
feature
|
||||
|
||||
connectedCallback(){
|
||||
this.updateContent()
|
||||
}
|
||||
|
||||
updateContent(){
|
||||
this.replaceChildren(TEMPLATE.content.cloneNode(true))
|
||||
|
||||
let header = this.querySelector("camp-feature-short-header")
|
||||
header.feature = this.feature
|
||||
header.updateContent()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define("camp-feature", FeatureElement)
|
||||
Loading…
Add table
Add a link
Reference in a new issue