diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2025-10-17 00:26:39 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2025-10-18 14:10:14 +0200 |
commit | 354e8aa6abfcc37452c8bcf8bab2d481612bb750 (patch) | |
tree | f72d8f2e9fe57283c4373122311771e9e361dcac /main.js | |
parent | 7f74020caf44589bcd68a07378823848c6daea03 (diff) |
Add an help modal with instructions how to use the map.
Diffstat (limited to 'main.js')
-rw-r--r-- | main.js | 521 |
1 files changed, 304 insertions, 217 deletions
@@ -168,11 +168,16 @@ const CONTAINER_STOPEVENT = MAP.getViewport().getElementsByClassName('ol-overlay CONTAINER_STOPEVENT.appendChild(document.getElementById('zoom-control')); CONTAINER_STOPEVENT.appendChild(CONTAINER_MAP); CONTAINER_STOPEVENT.appendChild(document.getElementById('info-modal')); + CONTAINER_STOPEVENT.appendChild(document.getElementById('help-modal')); const info_backdrop = document.createElement('div'); CONTAINER_STOPEVENT.appendChild(info_backdrop); info_backdrop.id = 'info-modal-backdrop'; + const help_backdrop = document.createElement('div'); + CONTAINER_STOPEVENT.appendChild(help_backdrop); + help_backdrop.id = 'help-modal-backdrop'; + const age_filter = document.createElement('div'); age_filter.id = 'age-filter-modal'; age_filter.classList.add('modal'); @@ -340,6 +345,7 @@ if (window.location === window.parent.location) { btn.classList.add('btn', classInactive); btn.setAttribute('aria-label', btn.title); MAP.addControl(control); + control.element.id = 'fullscreen-toggle'; /* for the help dialog */ control.addEventListener('enterfullscreen', function() { /* dispose popover as entering fullscreen messes up its position */ @@ -423,260 +429,341 @@ if (window.location === window.parent.location) { }; } -const decodeEmailAddress = function(element) { - const MAILTO = 'mailto:'; - const CLASSNAME = 'email-address-b64'; - for (const a of element.getElementsByClassName(CLASSNAME)) { - if (a.tagName.toLowerCase() === 'a' && a.href.toLowerCase().startsWith(MAILTO)) { - let href = MAILTO; - for (const part of a.href.substr(MAILTO.length).split(/\s+/)) { - switch (part) { - case '__AT__': - href += '@'; - break; - case '__DOT__': - href += '.'; - break; - default: - href += atob(part); - } - } - a.href = href; - a.classList.remove(CLASSNAME); - } - } -}; - -/* info button */ +/* info and help buttons */ (function() { - const div = document.createElement('div'); - MENU.appendChild(div); - div.id = 'info-button'; - div.classList.add('ol-unselectable', 'ol-control'); + const add_button = function(x) { + const div = document.createElement('div'); + MENU.appendChild(div); + div.id = x.id + '-button'; + div.classList.add('ol-unselectable', 'ol-control'); - const btn = document.createElement('button'); - div.appendChild(btn); - btn.type = 'button'; - btn.setAttribute('aria-expanded', 'false'); - btn.title = 'Källor och licensinformation'; - btn.setAttribute('aria-label', btn.title); - btn.classList.add('btn', 'btn-light'); + const btn = document.createElement('button'); + div.appendChild(btn); + btn.type = 'button'; + btn.setAttribute('aria-expanded', 'false'); + btn.title = x.title; + btn.setAttribute('aria-label', btn.title); + btn.classList.add('btn', 'btn-light'); - const i = document.createElement('i'); - btn.appendChild(i); - i.classList.add('bi', 'bi-info-lg'); + const i = document.createElement('i'); + btn.appendChild(i); + i.classList.add('bi', 'bi-' + x.bi); - const panel = document.getElementById('info-modal'); - const modal = new Modal(panel, { - backdrop: false, - }); - decodeEmailAddress(panel); + const panel = document.getElementById(x.id + '-modal'); + const modal = new Modal(panel, { + backdrop: false, + }); - const backdrop = document.getElementById('info-modal-backdrop'); - backdrop.onclick = function() { - modal.hide(); - }; + const backdrop = document.getElementById(x.id + '-modal-backdrop'); + backdrop.onclick = function() { + modal.hide(); + }; - panel.addEventListener('show.bs.modal', function() { - backdrop.classList.add('modal-backdrop', 'show'); - btn.setAttribute('aria-expanded', 'true'); - btn.classList.replace('btn-light', 'btn-dark'); - }); - panel.addEventListener('hide.bs.modal', function() { - /* XXX workaround for https://github.com/twbs/bootstrap/issues/41005#issuecomment-2585390544 */ - const activeElement = document.activeElement; - if (activeElement instanceof HTMLElement) { - activeElement.blur(); - } - }); - panel.addEventListener('hidden.bs.modal', function() { - btn.classList.replace('btn-dark', 'btn-light'); - btn.setAttribute('aria-expanded', 'false'); - backdrop.classList.remove('modal-backdrop', 'show'); - infoMetadataAccordions.forEach(function(x, idx) { - /* collapse all accordions */ - const body = x.element.parentNode.parentNode; - const name = 'info-accordion-collapse-' + idx; - if (body.id === name) { - body.classList.remove('show'); + panel.addEventListener('show.bs.modal', function() { + backdrop.classList.add('modal-backdrop', 'show'); + btn.setAttribute('aria-expanded', 'true'); + btn.classList.replace('btn-light', 'btn-dark'); + }); + panel.addEventListener('hide.bs.modal', function() { + /* XXX workaround for https://github.com/twbs/bootstrap/issues/41005#issuecomment-2585390544 */ + const activeElement = document.activeElement; + if (activeElement instanceof HTMLElement) { + activeElement.blur(); } - if (body.parentNode !== null) { - const headers = body.parentNode.getElementsByClassName('accordion-header'); - for (let i = 0; i < headers.length; i++) { - const buttons = headers[i].getElementsByClassName('accordion-button'); - for (let j = 0; j < buttons.length; j++) { - const btn = buttons[j]; - if (btn.getAttribute('data-bs-target') === '#' + name) { - btn.setAttribute('aria-expanded', 'false'); - btn.classList.add('collapsed'); - } + }); + + panel.addEventListener('hidden.bs.modal', function() { + btn.classList.replace('btn-dark', 'btn-light'); + btn.setAttribute('aria-expanded', 'false'); + backdrop.classList.remove('modal-backdrop', 'show'); + }); + + btn.onclick = function() { + modal.show(); + }; + + /* de-obfuscate email address */ + const MAILTO = 'mailto:'; + const CLASSNAME = 'email-address-b64'; + for (const a of panel.getElementsByClassName(CLASSNAME)) { + if (a.tagName.toLowerCase() === 'a' && a.href.toLowerCase().startsWith(MAILTO)) { + let href = MAILTO; + for (const part of a.href.substr(MAILTO.length).split(/\s+/)) { + switch (part) { + case '__AT__': + href += '@'; + break; + case '__DOT__': + href += '.'; + break; + default: + href += atob(part); } } + a.href = href; + a.classList.remove(CLASSNAME); } + } + + return [panel, btn, modal]; + }; + + /* info button */ + (function() { + const [panel, btn, modal] = add_button({ + id: 'info', + title: 'Källor och licensinformation', + bi: 'info-lg', }); - }); - const dateFormatter = new Intl.DateTimeFormat(LOCALE); - btn.onclick = function() { - infoMetadataAccordions.forEach((x) => x.element.replaceChildren()); - modal.show(); - Promise.allSettled(Object.entries(mapLayers).map(function([grp,lyr]) { - const baseurl = lyr?.getSource?.()?.get?.('baseurl'); - if (baseurl == null) { - return new Promise(() => { throw new Error(`Unknown source for "${grp}"`); }); - } - return fetch(new URL('metadata.json', baseurl)) - .then(function(resp0) { - if (resp0.status === 200) { - return resp0.json().then((x) => [grp,x]); - } else { - throw new Error(`${resp0.url} [${resp0.status}]`); - } - }); - })) - .then(function(rs) { - const metadata = Object.fromEntries(rs.filter(function(r) { - if (r.status === 'fulfilled') { - return true; - } else if (r.status === 'rejected') { - console.log(r.reason); - } - return false; - }).map((r) => r.value)); - - infoMetadataAccordions.forEach(function(x) { - const ul = x.element; - const groupnames = new Set(); - const last_updated = []; - x.items.forEach(function([groupname]) { - const layer_group = metadata[groupname]; - if (layer_group == null) { - return; - } - if (!groupnames.has(groupname)) { - groupnames.add(groupname); - if (layer_group.last_updated != null) { - last_updated.push(layer_group.last_updated); + panel.addEventListener('hidden.bs.modal', function() { + infoMetadataAccordions.forEach(function(x, idx) { + /* collapse all accordions */ + const body = x.element.parentNode.parentNode; + const name = 'info-accordion-collapse-' + idx; + if (body.id === name) { + body.classList.remove('show'); + } + if (body.parentNode !== null) { + const headers = body.parentNode.getElementsByClassName('accordion-header'); + for (let i = 0; i < headers.length; i++) { + const buttons = headers[i].getElementsByClassName('accordion-button'); + for (let j = 0; j < buttons.length; j++) { + const btn = buttons[j]; + if (btn.getAttribute('data-bs-target') === '#' + name) { + btn.setAttribute('aria-expanded', 'false'); + btn.classList.add('collapsed'); } } - }); - if (last_updated.length > 0) { - /* show creation time of the MVT layers */ - const li = document.createElement('li'); - li.classList.add('list-group-item', 'text-muted'); - ul.appendChild(li); - const i = document.createElement('i'); - i.classList.add('bi', 'bi-map'); - li.appendChild(i); - const t = document.createTextNode( - ' Lokalt skikt (vectiler) genererades ' + - last_updated - .sort() - .map((ts) => dateFormatter.format(new Date(ts))) - .join('; ') + '.' - ); - li.appendChild(t); } + } + }); + }); - const source_files = new Set(); - x.items.forEach(function([groupname, layername]) { - /* for each source file associated with the accordion header, show copyright, license and timing information */ - const layer_group = metadata[groupname]; - if (layer_group?.layers == null || layer_group?.source_files == null) { - return; + const dateFormatter = new Intl.DateTimeFormat(LOCALE); + btn.onclick = function() { + infoMetadataAccordions.forEach((x) => x.element.replaceChildren()); + modal.show(); + Promise.allSettled(Object.entries(mapLayers).map(function([grp,lyr]) { + const baseurl = lyr?.getSource?.()?.get?.('baseurl'); + if (baseurl == null) { + return new Promise(() => { throw new Error(`Unknown source for "${grp}"`); }); + } + return fetch(new URL('metadata.json', baseurl)) + .then(function(resp0) { + if (resp0.status === 200) { + return resp0.json().then((x) => [grp,x]); + } else { + throw new Error(`${resp0.url} [${resp0.status}]`); } - const def = layer_group.layers[layername]; - if (def?.source_files == null) { - return; + }); + })) + .then(function(rs) { + const metadata = Object.fromEntries(rs.filter(function(r) { + if (r.status === 'fulfilled') { + return true; + } else if (r.status === 'rejected') { + console.log(r.reason); } - def.source_files.forEach(function(source_file) { - if (source_files.has(source_file)) { + return false; + }).map((r) => r.value)); + + infoMetadataAccordions.forEach(function(x) { + const ul = x.element; + const groupnames = new Set(); + const last_updated = []; + x.items.forEach(function([groupname]) { + const layer_group = metadata[groupname]; + if (layer_group == null) { return; } - const x = layer_group.source_files[source_file]; - source_files.add(source_file); - + if (!groupnames.has(groupname)) { + groupnames.add(groupname); + if (layer_group.last_updated != null) { + last_updated.push(layer_group.last_updated); + } + } + }); + if (last_updated.length > 0) { + /* show creation time of the MVT layers */ const li = document.createElement('li'); - li.classList.add('list-group-item'); + li.classList.add('list-group-item', 'text-muted'); ul.appendChild(li); - const h = document.createElement('h6'); - li.appendChild(h); - if (x.description != null) { - const t = document.createTextNode(x.description); - h.appendChild(t); - } + const i = document.createElement('i'); + i.classList.add('bi', 'bi-map'); + li.appendChild(i); + const t = document.createTextNode( + ' Lokalt skikt (vectiler) genererades ' + + last_updated + .sort() + .map((ts) => dateFormatter.format(new Date(ts))) + .join('; ') + '.' + ); + li.appendChild(t); + } - if (x.copyright != null) { - const p = document.createElement('p'); - li.appendChild(p); - const t = document.createTextNode(x.copyright); - p.appendChild(t); + const source_files = new Set(); + x.items.forEach(function([groupname, layername]) { + /* for each source file associated with the accordion header, show copyright, license and timing information */ + const layer_group = metadata[groupname]; + if (layer_group?.layers == null || layer_group?.source_files == null) { + return; } + const def = layer_group.layers[layername]; + if (def?.source_files == null) { + return; + } + def.source_files.forEach(function(source_file) { + if (source_files.has(source_file)) { + return; + } + const x = layer_group.source_files[source_file]; + source_files.add(source_file); + + const li = document.createElement('li'); + li.classList.add('list-group-item'); + ul.appendChild(li); + const h = document.createElement('h6'); + li.appendChild(h); + if (x.description != null) { + const t = document.createTextNode(x.description); + h.appendChild(t); + } - if (x.license != null) { - const p = document.createElement('p'); - li.appendChild(p); - p.appendChild(document.createTextNode('Licensvillkor: ')); - const t = document.createTextNode(x.license.name); - if (x.license.url == null) { + if (x.copyright != null) { + const p = document.createElement('p'); + li.appendChild(p); + const t = document.createTextNode(x.copyright); p.appendChild(t); - } else { + } + + if (x.license != null) { + const p = document.createElement('p'); + li.appendChild(p); + p.appendChild(document.createTextNode('Licensvillkor: ')); + const t = document.createTextNode(x.license.name); + if (x.license.url == null) { + p.appendChild(t); + } else { + const a = document.createElement('a'); + a.href = x.license.url; + a.target = '_blank'; + a.appendChild(t); + p.appendChild(a); + } + } + + if (x.product_url != null) { + const p = document.createElement('p'); + li.appendChild(p); + const t = document.createTextNode('Produktlänk '); + const i = document.createElement('i'); + i.classList.add('bi', 'bi-box-arrow-up-right'); const a = document.createElement('a'); - a.href = x.license.url; + a.href = x.product_url; a.target = '_blank'; a.appendChild(t); + a.appendChild(i); p.appendChild(a); } - } - - if (x.product_url != null) { - const p = document.createElement('p'); - li.appendChild(p); - const t = document.createTextNode('Produktlänk '); - const i = document.createElement('i'); - i.classList.add('bi', 'bi-box-arrow-up-right'); - const a = document.createElement('a'); - a.href = x.product_url; - a.target = '_blank'; - a.appendChild(t); - a.appendChild(i); - p.appendChild(a); - } - if (x.last_modified != null) { - const p = document.createElement('p'); - p.classList.add('small', 'text-muted'); - li.appendChild(p); - const i = document.createElement('i'); - i.classList.add('bi', 'bi-file-earmark-code'); - p.appendChild(i); - p.appendChild(document.createTextNode(' ')); - const t0 = document.createTextNode('Källfil'); - if (x.url == null) { - p.appendChild(t0); - } else { - const a = document.createElement('a'); - p.appendChild(a); + if (x.last_modified != null) { + const p = document.createElement('p'); + p.classList.add('small', 'text-muted'); + li.appendChild(p); const i = document.createElement('i'); - i.classList.add('bi', 'bi-box-arrow-up-right'); - a.appendChild(t0); - a.appendChild(document.createTextNode(' ')); - a.appendChild(i); - a.href = x.url; - a.target = '_blank'; + i.classList.add('bi', 'bi-file-earmark-code'); + p.appendChild(i); + p.appendChild(document.createTextNode(' ')); + const t0 = document.createTextNode('Källfil'); + if (x.url == null) { + p.appendChild(t0); + } else { + const a = document.createElement('a'); + p.appendChild(a); + const i = document.createElement('i'); + i.classList.add('bi', 'bi-box-arrow-up-right'); + a.appendChild(t0); + a.appendChild(document.createTextNode(' ')); + a.appendChild(i); + a.href = x.url; + a.target = '_blank'; + } + const t1 = document.createTextNode(' ändrades senast '); + p.appendChild(t1); + const td = document.createTextNode(dateFormatter.format(new Date(x.last_modified))); + p.appendChild(td); + const t2 = document.createTextNode('.'); + p.appendChild(t2); } - const t1 = document.createTextNode(' ändrades senast '); - p.appendChild(t1); - const td = document.createTextNode(dateFormatter.format(new Date(x.last_modified))); - p.appendChild(td); - const t2 = document.createTextNode('.'); - p.appendChild(t2); - } + }); }); }); }); - }); - }; + }; + })(); + + /* help button */ + (function() { + const [panel] = add_button({ + id: 'help', + title: 'Hjälp med att använda kartan', + bi: 'question-circle', + }); + + /* Use the text from the .html file but ensure that buttons are + * listed in the same order as the menu, and spell titles out. This + * avoids duplication and avoids that things would get out of sync */ + const button_map = {}; + const ol = panel.querySelector('#help-describe-functions'); + if (ol != null && ol.tagName.toLowerCase() === 'ol') { + for (const li of ol.children) { + const id = li.getAttribute('data-for-button'); + if (id == null || id === '') { + continue; + } + button_map[id] = li; + } + } + + for (const node of MENU.children) { + if (node.id == null || node.id === '') { + continue + } + const btn = node.getElementsByTagName('button')[0]; + if (btn == null || btn.tagName.toLowerCase() !== 'button') { + continue; + } + const btn2 = btn.cloneNode(true); + const title = btn2.title; + btn2.id = btn2.title = ''; + for (const attr of btn.attributes) { + if (attr.name.toLowerCase().startsWith('aria-') || attr.name === 'id' || attr.name === 'title') { + btn2.removeAttribute(attr.name); + } + } + + const h = document.createElement('h6'); + h.classList.add('help-button-description'); + h.appendChild(btn2); + if (title != null && title != '') { + const t = document.createTextNode(title) + h.appendChild(t); + } + btn2.classList.add('help-button'); + + ol.insertAdjacentElement('beforebegin', h); + + const li = button_map[node.id]; + if (li != null) { + /* move <li>'s children (paragraphs) to the main text */ + while (li.children.length > 0) { + ol.insertAdjacentElement('beforebegin', li.firstElementChild); + } + } + } + ol.remove(); + })(); })(); /* we're all set, show the control container now */ |