diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2025-06-15 20:09:00 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2025-06-15 23:42:52 +0200 |
commit | c2458cdca0df1147e12e4dd75a28c712d898cee3 (patch) | |
tree | dd0f2a3e689170c6f6d76ea21257904f9f24d975 | |
parent | 42414ddd41eb57edd5128081b8e3add0354c6b3d (diff) |
Setup a timeout to adjust the time filter at midnight.
We use usual integer comparison against the feature's timestamp
internally, so for relative filters we need to adjust boundaries at
midnight. The help text needs updating too if the modal is open and
the relative dialog checked.
-rw-r--r-- | main.js | 112 |
1 files changed, 94 insertions, 18 deletions
@@ -662,11 +662,19 @@ const ageFilterSettings = (function() { } }; return Object.seal({ - active: false, + _active: false, + get active() { + return this._active; + }, + set active(b) { + ageFilterSetActive(b); + }, type: 'relative', operator: '<=', quantity: 1, unit: 'y', + from: null, + to: null, show_unknown: false, _min_ts: null, _max_ts: null, @@ -2655,7 +2663,7 @@ const STYLES = {}; ageFilterSettings.quantity = parseInt(m0[2], 10); ageFilterSettings.unit = m0[3]; ageFilterSettings.setupMinMax(); - ageFilterSettings.active = true; + ageFilterSettings._active = true; /* don't call the setter as it's not initialized yet */ return; } const m1 = /^([0-9]{8})-([0-9]{8})$/.exec(value); /* YYYYMMDD */ @@ -2670,7 +2678,7 @@ const STYLES = {}; ageFilterSettings.from = parse_date(m1[1]); ageFilterSettings.to = parse_date(m1[2]); ageFilterSettings.setupMinMax(); - ageFilterSettings.active = true; + ageFilterSettings._active = true; /* don't call the setter as it's not initialized yet */ return; } //console.log(`Ignoring invalid value for 'age-filter' parameter: ${value}`); @@ -4945,9 +4953,11 @@ const disposePopover = (function() { })(); /* age filter dialog */ -(function() { +const ageFilterSetActive = (function() { const panel = document.getElementById('age-filter-modal'); + let updateHelpTextTimeoutID = null; + const updateHelpTextState = Object.seal({ value: null }); const dialog_setup = (function() { const dialog = document.createElement('div'); dialog.classList.add('modal-dialog', 'modal-dialog-centered'); @@ -5044,6 +5054,10 @@ const disposePopover = (function() { radio.checked = true; radio.setAttribute('aria-expanded', 'true'); + if (type_choice === type_choices.relative) { + type_choice._update_helptext(); + } + Object.values(type_choices).forEach(function(x) { const isSame = radio.isEqualNode(x.radio); if (isSame) { @@ -5145,7 +5159,6 @@ const disposePopover = (function() { return null; } else { /* update help text */ - /* TODO auto update the date passed midnight (if the modal is open) */ span_date.innerHTML = format_date(d); span_direction.innerHTML = { '>=':'tidigare', '<=':'senare' }[operator]; p.classList.remove('invisible'); @@ -5290,12 +5303,6 @@ const disposePopover = (function() { btn_cancel.onclick = function() { /* deactivate deactivate the filter but preserve its settings */ ageFilterSettings.active = false; - Object.values(mapLayers).forEach(function(lyr) { - if (lyr?.get('canFilterByAge')) { - lyr.changed(); - } - }); - const params = new URLSearchParams(window.location.hash.substring(1)); params.delete('age-filter'); params.delete('show-unknown-age'); @@ -5329,14 +5336,7 @@ const disposePopover = (function() { } ageFilterSettings.type = filter_type; ageFilterSettings.show_unknown = show_unknown_age.checked; - ageFilterSettings.setupMinMax(); ageFilterSettings.active = true; - /* TODO auto update the filter passed midnight (if active) */ - Object.values(mapLayers).forEach(function(lyr) { - if (lyr?.get('canFilterByAge')) { - lyr.changed(); - } - }); const params = new URLSearchParams(window.location.hash.substring(1)); params.set('age-filter', param); @@ -5346,6 +5346,15 @@ const disposePopover = (function() { modal.hide(); }; + const updateHelpTextTimeout = function cb() { + const type_choice = type_choices.relative; + const [ms, b] = getDelay(updateHelpTextState); + if (type_choice.radio.checked && b) { + type_choice._update_helptext(); + } + updateHelpTextTimeoutID = setTimeout(cb, ms); + }; + /* Now that all elements have been added to the form, click the radio * button to show the relevant <div>, mark its fields as required and * fills them. The function is run whenever the modal is shown. */ @@ -5372,9 +5381,32 @@ const disposePopover = (function() { break; } } + + if (updateHelpTextTimeoutID == null) { + const ms = getDelay(updateHelpTextState)[0]; + updateHelpTextTimeoutID = setTimeout(updateHelpTextTimeout, ms); + } }; })(); + /* Retun the number of milliseconds left for the next full 15min at the + * wall clock, along with a boolean indicating whether we just passed + * midnight. + * Keep using setTimeout(,15min) since unlike setInterval() it allows us to avoid clock + * skews by adjusting delays */ + const getDelay = function(state) { + const i = 900, d = new Date(); /* at *:00,15,30,45:00.50 */ + const sec = (d.getHours()*60 + d.getMinutes())*60 + d.getSeconds(); + /* add 50ms to ensure formatting the date doesn't end up on the previous period */ + const ms = (Math.floor(sec/i)*i + i - sec)*1000 - d.getMilliseconds() + 50; + const v = state.value, v2 = d.getDate(); + const b = v == null || v !== v2; + if (b) { + state.value = v2; + } + return [ms, b]; + }; + const modal = new Modal(panel, { backdrop: false, }); @@ -5404,6 +5436,11 @@ const disposePopover = (function() { if (!ageFilterSettings.active) { btn.classList.replace('btn-dark', 'btn-light'); } + if (updateHelpTextTimeoutID != null) { + clearTimeout(updateHelpTextTimeoutID); + updateHelpTextState.value = null; + updateHelpTextTimeoutID = null; + } btn.setAttribute('aria-expanded', 'false'); backdrop.classList.remove('modal-backdrop', 'show'); }); @@ -5413,4 +5450,43 @@ const disposePopover = (function() { dialog_setup(); modal.show(); }; + + return (function() { /* setter for ageFilterSettings.active */ + let timeoutID = null; + const state = { value: null }; + const fun = function cb() { + const [ms, b] = getDelay(state); + if (b && ageFilterSettings.type === 'relative') { + ageFilterSettings.setupMinMax(); + changed(); + } + timeoutID = setTimeout(cb, ms); + }; + const changed = function() { + Object.values(mapLayers) + .filter((lyr) => lyr?.get('canFilterByAge')) + .forEach((lyr) => lyr.changed()); + }; + const setter = function(active) { + if (ageFilterSettings._active !== active) { + if (active) { + ageFilterSettings.setupMinMax(); + } + ageFilterSettings._active = active; + changed(); + } + if (active && ageFilterSettings.type === 'relative') { + if (timeoutID == null) { + timeoutID = setTimeout(fun, getDelay(state)[0]); + } + } else if (timeoutID != null) { + clearTimeout(timeoutID); + state.value = null; + timeoutID = null; + } + }; + /* initial activation from param URL */ + setter(ageFilterSettings._active); + return setter; + })(); })(); |