aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2025-06-15 20:09:00 +0200
committerGuilhem Moulin <guilhem@fripost.org>2025-06-15 23:42:52 +0200
commitc2458cdca0df1147e12e4dd75a28c712d898cee3 (patch)
treedd0f2a3e689170c6f6d76ea21257904f9f24d975
parent42414ddd41eb57edd5128081b8e3add0354c6b3d (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.js112
1 files changed, 94 insertions, 18 deletions
diff --git a/main.js b/main.js
index 3455e46..c0a3186 100644
--- a/main.js
+++ b/main.js
@@ -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;
+ })();
})();