Ajoute "Variable globale" au sélecteur "Valeur fixe / Donnée d'un autre objet"

Le sélecteur de valeur de comparaison (tout contrôle "..._valeur" : filtre
du Répéteur, Donnée liée, condition de visibilité) proposait déjà une
valeur fixe ou le champ d'un AUTRE objet - manquait la possibilité de
comparer à une variable globale (voir db/global_vars/), qui change elle
aussi en cours de partie mais n'est rattachée à aucun objet précis.

Nouvelle syntaxe interne "{{$nom_variable}}" (le "$" la distingue sans
ambiguïté de "{{Objet.champ}}", qui attend toujours un point) :
_resolve_filter_value (filter_repeater_rows.py) va lire sa valeur actuelle
via db.get_global_variable, comme "{{Objet.champ}}" le fait déjà pour un
champ d'objet. Le panneau de propriétés gagne un troisième mode
"Variable globale" à côté de "Valeur fixe"/"Donnée d'un autre objet",
avec la liste déroulante des variables existantes.

Corrige au passage un bug latent découvert en testant bout en bout : un
Répéteur SANS modèle de ligne (texte brut avec {{champ}}) plantait en
mode jouable avec TypeError - render_repeater.py substitue lui aussi
directement les {{champ}} du ctx dans ce cas (repli), et ce ctx porte
aussi _forge_play_mode (un booléen, voir render_element_html.py) depuis
l'ajout de la condition de visibilité - déjà corrigé pour le chemin
générique (apply_ctx.py) mais pas pour ce chemin séparé.

Le sélecteur de champ pour insérer {{champ}} dans "Contenu" (demandé dans
le même message) existe déjà depuis un tour précédent (voir
insertFieldAtCursor()) - vérifié toujours fonctionnel.

Ajoute tests/test_filter_value_global_variable.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-25 16:51:18 +02:00
co-authored by Claude Sonnet 5
parent 6894c5fc95
commit 7d48445443
4 changed files with 166 additions and 15 deletions
+47 -12
View File
@@ -684,6 +684,7 @@
<select class="filterValueMode" onchange="onFilterValueModeChange(this)">
<option value="fixe">Valeur fixe</option>
<option value="dynamique">Donnée d'un autre objet (change en jeu)</option>
<option value="variable">Variable globale (change en jeu)</option>
</select>
</div>
<div class="filterValueFixe" style="margin-top:6px;">
@@ -704,6 +705,19 @@
</select>
</div>
</div>
<div class="filterValueVariable" style="display:none; margin-top:6px;">
<div class="select is-small is-fullwidth">
<select class="filterValueVarSel" onchange="onFilterValueVarChange(this)">
<option value="">— variable —</option>
{% for v in global_variables %}
<option value="{{ v.name }}">{{ v.name }}</option>
{% endfor %}
</select>
</div>
{% if not global_variables %}
<p class="help">Aucune variable globale pour l'instant — <a href="{{ url_for('game_variables', slug=game.slug) }}" target="_blank">crées-en une ↗</a></p>
{% endif %}
</div>
</div>
{% elif c.type in ('text', 'url') %}
<div class="control">
@@ -1700,6 +1714,11 @@ function bindDefinitionFieldSelects(defSelId, fieldSelIds) {
var _FILTER_REF_OPEN = '{' + '{';
var _FILTER_REF_CLOSE = '}' + '}';
var _FILTER_REF_RE = new RegExp('^\\{\\{\\s*([^.{}]+)\\.([^.{}]+)\\s*\\}\\}$');
// Une paire d'accolades doublées entourant "$nom_variable" (voir
// _VAR_REF_PATTERN côté Python, filter_repeater_rows.py) référence une
// variable globale — le "$" la distingue sans ambiguïté de _FILTER_REF_RE
// ci-dessus, qui attend toujours un point ("Objet.champ").
var _VAR_REF_RE = new RegExp('^\\{\\{\\s*\\$([^.{}]+)\\s*\\}\\}$');
function _filterValueDefinitionId(objName) {
var opt = document.querySelector('.filterValueObjSel option[value="' + CSS.escape(objName) + '"]');
@@ -1721,18 +1740,23 @@ function updateFilterValueFromDynamic(wrap) {
input.dispatchEvent(new Event('input', { bubbles: true }));
}
function updateFilterValueFromVariable(wrap) {
var varSel = wrap.querySelector('.filterValueVarSel');
var input = wrap.querySelector('.filterValueFixe input');
input.value = varSel.value ? (_FILTER_REF_OPEN + '$' + varSel.value + _FILTER_REF_CLOSE) : '';
input.dispatchEvent(new Event('input', { bubbles: true }));
}
function onFilterValueModeChange(sel) {
var wrap = sel.closest('.filterValueControl');
var fixe = wrap.querySelector('.filterValueFixe');
var dyn = wrap.querySelector('.filterValueDynamique');
if (sel.value === 'dynamique') {
fixe.style.display = 'none';
dyn.style.display = 'block';
updateFilterValueFromDynamic(wrap);
} else {
fixe.style.display = 'block';
dyn.style.display = 'none';
}
var varWrap = wrap.querySelector('.filterValueVariable');
fixe.style.display = sel.value === 'fixe' ? 'block' : 'none';
dyn.style.display = sel.value === 'dynamique' ? 'block' : 'none';
varWrap.style.display = sel.value === 'variable' ? 'block' : 'none';
if (sel.value === 'dynamique') updateFilterValueFromDynamic(wrap);
else if (sel.value === 'variable') updateFilterValueFromVariable(wrap);
}
function onFilterValueObjChange(sel) {
@@ -1745,19 +1769,30 @@ function onFilterValueFieldChange(sel) {
updateFilterValueFromDynamic(sel.closest('.filterValueControl'));
}
function onFilterValueVarChange(sel) {
updateFilterValueFromVariable(sel.closest('.filterValueControl'));
}
function initFilterValuePickers() {
document.querySelectorAll('.filterValueControl').forEach(function (wrap) {
var modeSel = wrap.querySelector('.filterValueMode');
var objSel = wrap.querySelector('.filterValueObjSel');
var fieldSel = wrap.querySelector('.filterValueFieldSel');
var varSel = wrap.querySelector('.filterValueVarSel');
var current = (wrap.dataset.currentValue || '').trim();
var m = _FILTER_REF_RE.exec(current);
if (m) {
var varMatch = _VAR_REF_RE.exec(current);
var objMatch = _FILTER_REF_RE.exec(current);
if (varMatch) {
modeSel.value = 'variable';
wrap.querySelector('.filterValueFixe').style.display = 'none';
wrap.querySelector('.filterValueVariable').style.display = 'block';
varSel.value = varMatch[1].trim();
} else if (objMatch) {
modeSel.value = 'dynamique';
wrap.querySelector('.filterValueFixe').style.display = 'none';
wrap.querySelector('.filterValueDynamique').style.display = 'block';
objSel.value = m[1].trim();
populateFilterValueFields(fieldSel, _filterValueDefinitionId(objSel.value), m[2].trim());
objSel.value = objMatch[1].trim();
populateFilterValueFields(fieldSel, _filterValueDefinitionId(objSel.value), objMatch[2].trim());
} else {
modeSel.value = 'fixe';
}