aboutsummaryrefslogtreecommitdiffstats
path: root/main.js
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2025-10-17 00:26:39 +0200
committerGuilhem Moulin <guilhem@fripost.org>2025-10-18 14:10:14 +0200
commit354e8aa6abfcc37452c8bcf8bab2d481612bb750 (patch)
treef72d8f2e9fe57283c4373122311771e9e361dcac /main.js
parent7f74020caf44589bcd68a07378823848c6daea03 (diff)
Add an help modal with instructions how to use the map.
Diffstat (limited to 'main.js')
-rw-r--r--main.js521
1 files changed, 304 insertions, 217 deletions
diff --git a/main.js b/main.js
index 59f6143..93edd14 100644
--- a/main.js
+++ b/main.js
@@ -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 */