From fd88c1d218347602b00398b1e5d326c8078992e7 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 19 Jan 2024 04:00:22 +0100 Subject: Add accordions to the layer selection panel. --- main.js | 271 ++++++++++++++++++++++++++++++++++++++++++++++++-------------- style.css | 40 ++++++++++ 2 files changed, 252 insertions(+), 59 deletions(-) diff --git a/main.js b/main.js index c98179f..ce7c5cc 100644 --- a/main.js +++ b/main.js @@ -466,75 +466,84 @@ view.on('change', function(event) { }); -const styles = { - 'svk_lines': - [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) { - return new Style({ - zIndex: 2, - stroke: new Stroke({ - color: 'black', - width: width, - }), - }); - }), - 'svk_pylons': - [undefined, undefined, undefined, undefined, undefined] - .concat([3, 4, 5, 6, 8, 10, 15].map(function(radius) { +const layers = { + svk_lines: { + style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) { + return new Style({ + zIndex: 2, + stroke: new Stroke({ + color: 'black', + width: width, + }), + }); + }), + }, + svk_pylons: { + style: [undefined, undefined, undefined, undefined, undefined] + .concat([3, 4, 5, 6, 8, 10, 15].map(function(radius) { + return new Style({ + zIndex: 1, + image: new CircleStyle({ + radius: radius, + fill: new Fill({ + color: 'black', + }), + }), + }); + })), + }, + svk_stations: { + style: [5, 6, 8, 8, 10, 12, 12].map(function(radius) { return new Style({ - zIndex: 1, - image: new CircleStyle({ + zIndex: 0, + image: new RegularShape({ radius: radius, + points: 4, + angle: Math.PI/4, fill: new Fill({ color: 'black', }), }), }); - })), - 'svk_stations': - [5, 6, 8, 8, 10, 12, 12].map(function(radius) { - return new Style({ - zIndex: 0, - image: new RegularShape({ - radius: radius, - points: 4, - angle: Math.PI/4, + }) + .concat([.5, 1, 1.5, 2, 2].map(function(width) { + return new Style({ + zIndex: 0, fill: new Fill({ - color: 'black', + color: 'rgba(128, 128, 128, .7)', }), - }), - }); - }) - .concat([.5, 1, 1.5, 2, 2].map(function(width) { - return new Style({ - zIndex: 0, - fill: new Fill({ - color: 'rgba(128, 128, 128, .7)', - }), - stroke: new Stroke({ - width: width, - color: 'rgb(0, 0, 0)', - }), - }); - })), + stroke: new Stroke({ + width: width, + color: 'rgb(0, 0, 0)', + }), + }); + })), + }, }; -map.addLayer(new VectorTileLayer({ - source: new VectorTile({ - url: '/public/xyztiles/{z}/{x}/{y}.pbf', - format: new MVT({ - layers: Object.keys(styles), - }), - projection: projection, - wrapX: false, - transition: 0, - tileGrid: createXYZ({ - extent: extent, - tileSize: 1024, - maxResolution: 1024, /* = 1048576/1024 */ - minZoom: 0, - maxZoom: 9, - }), +const vectorSource = new VectorTile({ + url: '/public/xyztiles/{z}/{x}/{y}.pbf', + format: new MVT({ + layers: Object.keys(layers), + }), + projection: projection, + wrapX: false, + transition: 0, + tileGrid: createXYZ({ + extent: extent, + tileSize: 1024, + maxResolution: 1024, /* = 1048576/1024 */ + minZoom: 0, + maxZoom: 9, }), +}); + +const styles = Object.keys(layers).reduce(function(result, key) { + result[key] = layers[key].style; + return result; +}, {}); +map.addLayer(new VectorTileLayer({ + source: vectorSource, /* XXX switch to 'hybrid' if there are perf issues; but that seems to * put lines above points regardless of their respective z-index */ renderMode: 'vector', @@ -569,9 +578,153 @@ map.addLayer(new VectorTileLayer({ content.appendChild(body); body.classList.add('modal-body'); + const accordion = document.createElement('div'); + body.appendChild(accordion); + accordion.id = 'layer-selection-accordion'; + accordion.classList.add('accordion', 'accordion-flush'); + + const setIndeterminateAndChecked = function(input, children) { + const childrenStyles = Object.values(children.reduce(function(result, child) { + if (Array.isArray(child.layerName)) { + child.layerName.forEach(function(layerName) { + result[layerName] = (styles[layerName] !== undefined); + }); + } else { + result[child.layerName] = (styles[child.layerName] !== undefined); + } + return result; + }, {})); + input.indeterminate = children.length <= 1 ? false : childrenStyles.slice(1).some((v) => v !== childrenStyles[0]); + input.checked = childrenStyles.every((v) => v); + }; + + let accordionCollapseId = 0, inputId = 0; + const addAccordionItem = function(headerText, children) { + const item = document.createElement('div'); + accordion.appendChild(item); + item.classList.add('accordion-item'); + + const header = document.createElement('div'); + item.appendChild(header); + header.classList.add('accordion-header'); + + const btn = document.createElement('button'); + header.appendChild(btn); + + const collapse = document.createElement('div'); + item.appendChild(collapse); + collapse.id = 'accordion-collapse-' + accordionCollapseId++; + collapse.classList.add('accordion-collapse', 'collapse'); + // collapse.setAttribute('data-bs-parent', '#' + accordion.id); + + btn.type = 'button'; + btn.setAttribute('data-bs-toggle', 'collapse'); + btn.setAttribute('data-bs-target', '#' + collapse.id); + btn.setAttribute('aria-expanded', 'false'); + btn.setAttribute('aria-controls', collapse.id); + btn.classList.add('accordion-button', 'collapsed'); + + const span0 = document.createElement('span'); + btn.appendChild(span0); + span0.classList.add('form-check'); + span0.setAttribute('data-bs-toggle', 'collapse'); + span0.setAttribute('data-bs-target', ''); + + const input0 = document.createElement('input'); + span0.appendChild(input0); + input0.classList.add('form-check-input'); + input0.type = 'checkbox'; + input0.id = 'layer' + inputId++; + + const label0 = document.createElement('label'); + span0.appendChild(label0); + label0.classList.add('form-check-label'); + label0.setAttribute('for', input0.id); + + const text0 = document.createTextNode(headerText); + label0.appendChild(text0); + + setIndeterminateAndChecked(input0, children); + + const inputs = Object.values(children).map(function(child) { + const input = document.createElement('input'); + setIndeterminateAndChecked(input, [child]); + return input; + }); + + const body = document.createElement('div'); + collapse.appendChild(body); + body.classList.add('accordion-body'); + + const group = document.createElement('ul'); + body.appendChild(group); + group.classList.add('list-group', 'list-group-flush'); + + children.forEach(function(child, i) { + const li = document.createElement('li'); + group.appendChild(li); + li.classList.add('list-group-item'); + + const input = inputs[i]; + li.appendChild(input); + input.classList.add('form-check-input'); + input.type = 'checkbox'; + input.id = 'layer' + inputId++; + + const label = document.createElement('label'); + li.appendChild(label); + label.classList.add('form-check-label'); + label.setAttribute('for', input.id); + + const text = document.createTextNode(child.text); + label.appendChild(text); + }); + + input0.onclick = function(event) { + children.forEach(function(child, i) { + const layerNames = Array.isArray(child.layerName) ? child.layerName : [child.layerName]; + layerNames.forEach(function(layerName) { + if (input0.checked) { + styles[layerName] = layers[layerName].style; + inputs[i].checked = true; + } else { + delete styles[layerName]; + inputs[i].checked = false; + } + }); + }); + vectorSource.changed(); + }; + + inputs.forEach(function(input, i) { + input.onclick = function(event) { + const layerNames = Array.isArray(children[i].layerName) ? children[i].layerName : [children[i].layerName]; + layerNames.forEach(function(layerName) { + if (input.checked) { + styles[layerName] = layers[layerName].style; + } else { + delete styles[layerName]; + } + }); + + setIndeterminateAndChecked(input0, children); + vectorSource.changed(); + }; + }); + }; + + addAccordionItem('Transmissionsnät för el', [ + {layerName: ['svk_lines', 'svk_pylons'], text: 'Kraftledningar'}, + {layerName: 'svk_stations', text: 'Stationer'}, + ]); + (function() { + const item = document.createElement('div'); + accordion.appendChild(item); + item.classList.add('accordion-item'); + const div = document.createElement('div'); - body.appendChild(div); + item.appendChild(div); div.classList.add('form-check', 'form-switch'); const input = document.createElement('input'); @@ -579,7 +732,7 @@ map.addLayer(new VectorTileLayer({ input.classList.add('form-check-input'); input.type = 'checkbox'; input.setAttribute('role', 'switch'); - input.id = 'layer-basemap'; + input.id = 'layer' + inputId++; const label = document.createElement('label'); div.appendChild(label); diff --git a/style.css b/style.css index cce906c..5838586 100644 --- a/style.css +++ b/style.css @@ -255,3 +255,43 @@ html, body { .ol-overlaycontainer-stopevent .modal-backdrop.show { pointer-events: auto; } + +#layer-selection-panel .accordion { + --bs-accordion-active-bg: var(--bs-accordion-bg); + --bs-accordion-active-color: var(--bs-body-color); + --bs-accordion-btn-padding-x: 0; + --bs-accordion-btn-padding-y: 0; + --bs-accordion-btn-focus-box-shadow: none; + --bs-accordion-body-padding-x: 0; + --bs-accordion-body-padding-y: 0; +} +#layer-selection-panel .accordion-item { + border: none; +} +#layer-selection-panel .accordion-body { + padding-left: 1em; +} +#layer-selection-panel .accordion .list-group.list-group-flush > .list-group-item { + padding: 0; + border: none; +} +#layer-selection-panel .accordion-item .form-check { + padding-left: 0; + margin: 0.125rem 0 0 0; +} +#layer-selection-panel .accordion-item:first-child .form-check { + margin-top: 0; +} +#layer-selection-panel .accordion-header > .accordion-button[aria-expanded="true"] { + padding-bottom: 0.125rem; + margin-bottom: 0.125rem; +} +#layer-selection-panel .accordion-header > .accordion-button > .form-check > .form-check-input, +#layer-selection-panel .accordion > .accordion-item > .form-check > .form-check-input { + margin-left: 0; + float: none; +} +#layer-selection-panel .accordion-item .form-check > .form-check-input, +#layer-selection-panel .accordion-item .list-group-item > .form-check-input { + margin-right: .5em !important; +} -- cgit v1.2.3