Variables globales Objet/Tableau, lisibles via un chemin dans les conditions et filtres
Deux nouveaux types de variable globale (db/constants.py) : "objet" et
"tableau" — valeur stockée en JSON (colonne TEXT existante), avec
validation à la création/modification (db/global_vars/
coerce_structured_value.py) : un JSON invalide retombe sur un défaut sûr
("{}"/"[]") plutôt que de corrompre silencieusement la variable pour
toutes ses lectures suivantes. apply_variable_action.py n'a besoin
d'aucun changement — "definir_texte" écrit déjà n'importe quelle chaîne
telle quelle.
Nouvelle résolution de chemin, partagée (screens/rendering/
filter_repeater_rows.py::_resolve_variable_path) : navigue dans la
valeur JSON d'une variable selon un chemin ".champ"/"[index]" chaînable
(ex. ".arme.degats", "[0].valeur") — ne lève jamais, renvoie None si le
JSON est invalide ou qu'un segment du chemin ne correspond à rien.
Branchée à deux endroits, qui lisaient déjà une variable globale :
- La valeur de comparaison {{$nom_variable}} (filtres de Répéteur ET
Condition de visibilité, qui partagent le même
_resolve_filter_value()) accepte maintenant un chemin optionnel :
{{$perso.nom}}, {{$scores[0]}}. Le sélecteur "Variable globale" du
panneau de propriétés (screen_edit.html, .filterValueVariable) gagne un
champ "Chemin optionnel" à côté du choix de variable — même regex
étendue côté JS (_VAR_REF_RE) que côté Python (_VAR_REF_PATTERN), pour
que la valeur round-trip correctement à la réouverture du panneau.
- La variable VÉRIFIÉE par une Condition de visibilité en mode "variable"
(choisie via un <select>, pas la syntaxe {{$...}}) gagne son propre
nouveau contrôle "Chemin dans la variable" (visibility_condition_
controls.py) — nécessaire pour comparer un champ d'un Objet ou un
élément d'un Tableau, pas seulement la variable entière.
game_dashboard.html (onglet Variables) : la valeur par défaut de la barre
de création devient un <textarea> (fonctionne aussi bien pour un JSON
multi-ligne qu'un scalaire court), et la cellule "Valeur" du tableau des
variables existantes devient un <textarea> quand le type est objet/
tableau.
4 nouveaux tests (tests/test_variable_object_array.py) : validation JSON
à la création, filtre de Répéteur avec chemin chaîné, condition de
visibilité avec accès par index de tableau, chemin invalide/absent sans
plantage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b21f56e364
commit
aaf9954446
@@ -151,7 +151,7 @@
|
||||
<option value="{{ key }}">{{ val.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" name="default_value" placeholder="Valeur de départ">
|
||||
<textarea name="default_value" placeholder="Valeur de départ (JSON pour Objet/Tableau, ex. {} ou [])" rows="1"></textarea>
|
||||
<button type="submit" class="primary">Créer</button>
|
||||
</form>
|
||||
{% if variables %}
|
||||
@@ -172,7 +172,13 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" form="varEditForm{{ v.id }}" name="value" value="{{ v.value or '' }}" aria-label="Valeur de « {{ v.name }} »"></td>
|
||||
<td>
|
||||
{% if v.type in ('objet', 'tableau') %}
|
||||
<textarea form="varEditForm{{ v.id }}" name="value" rows="3" aria-label="Valeur de « {{ v.name }} »">{{ v.value or '' }}</textarea>
|
||||
{% else %}
|
||||
<input type="text" form="varEditForm{{ v.id }}" name="value" value="{{ v.value or '' }}" aria-label="Valeur de « {{ v.name }} »">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="fieldsTableActions">
|
||||
<button type="submit" form="varEditForm{{ v.id }}" class="button is-small">Enregistrer</button>
|
||||
<form method="post" action="{{ url_for('global_var_delete', slug=game.slug, var_id=v.id) }}"
|
||||
|
||||
@@ -646,7 +646,7 @@
|
||||
{% set ns.started = True %}
|
||||
{% endif %}
|
||||
<div class="field controlRow{{ ' visCondRow' if c.key.startswith('visibilite_cond_') and c.key != 'visibilite_cond_mode' else '' }}"
|
||||
{% if c.key == 'visibilite_cond_variable' %}data-vis-cond-scope="variable"
|
||||
{% if c.key in ('visibilite_cond_variable', 'visibilite_cond_variable_chemin') %}data-vis-cond-scope="variable"
|
||||
{% elif c.key in ('visibilite_cond_definition_id', 'visibilite_cond_champ') %}data-vis-cond-scope="objet"
|
||||
{% elif c.key in ('visibilite_cond_operateur', 'visibilite_cond_valeur') %}data-vis-cond-scope="any"
|
||||
{% endif %}>
|
||||
@@ -711,6 +711,10 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<!-- Utile seulement si la variable choisie est de type
|
||||
"objet"/"tableau" : sans chemin, c'est toute la variable
|
||||
(son JSON) qui est utilisée comme valeur de comparaison. -->
|
||||
<input class="input is-small filterValueVarPath" type="text" placeholder="Chemin optionnel (ex. .nom ou [0].valeur)" style="margin-top:6px;" oninput="updateFilterValueFromVariable(this.closest('.filterValueControl'))">
|
||||
{% if not global_variables %}
|
||||
<p class="help">Aucune variable globale pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='variables') }}" target="_blank">crées-en une ↗</a></p>
|
||||
{% endif %}
|
||||
@@ -1714,8 +1718,10 @@ 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*\\}\\}$');
|
||||
// ci-dessus, qui attend toujours un point ("Objet.champ"). Groupe 2 : un
|
||||
// chemin optionnel ".champ"/"[index]" (chaînable) à l'intérieur de la
|
||||
// variable, utile pour une variable "objet"/"tableau".
|
||||
var _VAR_REF_RE = new RegExp('^\\{\\{\\s*\\$([^.{}\\[\\]]+)((?:\\.[^.{}\\[\\]]+|\\[\\d+\\])*)\\s*\\}\\}$');
|
||||
|
||||
function _filterValueDefinitionId(objName) {
|
||||
var opt = document.querySelector('.filterValueObjSel option[value="' + CSS.escape(objName) + '"]');
|
||||
@@ -1739,8 +1745,10 @@ function updateFilterValueFromDynamic(wrap) {
|
||||
|
||||
function updateFilterValueFromVariable(wrap) {
|
||||
var varSel = wrap.querySelector('.filterValueVarSel');
|
||||
var pathInput = wrap.querySelector('.filterValueVarPath');
|
||||
var input = wrap.querySelector('.filterValueFixe input');
|
||||
input.value = varSel.value ? (_FILTER_REF_OPEN + '$' + varSel.value + _FILTER_REF_CLOSE) : '';
|
||||
var path = pathInput ? pathInput.value.trim() : '';
|
||||
input.value = varSel.value ? (_FILTER_REF_OPEN + '$' + varSel.value + path + _FILTER_REF_CLOSE) : '';
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
@@ -1776,6 +1784,7 @@ function initFilterValuePickers() {
|
||||
var objSel = wrap.querySelector('.filterValueObjSel');
|
||||
var fieldSel = wrap.querySelector('.filterValueFieldSel');
|
||||
var varSel = wrap.querySelector('.filterValueVarSel');
|
||||
var varPathInput = wrap.querySelector('.filterValueVarPath');
|
||||
var current = (wrap.dataset.currentValue || '').trim();
|
||||
var varMatch = _VAR_REF_RE.exec(current);
|
||||
var objMatch = _FILTER_REF_RE.exec(current);
|
||||
@@ -1784,6 +1793,7 @@ function initFilterValuePickers() {
|
||||
wrap.querySelector('.filterValueFixe').style.display = 'none';
|
||||
wrap.querySelector('.filterValueVariable').style.display = 'block';
|
||||
varSel.value = varMatch[1].trim();
|
||||
if (varPathInput) varPathInput.value = (varMatch[2] || '').trim();
|
||||
} else if (objMatch) {
|
||||
modeSel.value = 'dynamique';
|
||||
wrap.querySelector('.filterValueFixe').style.display = 'none';
|
||||
|
||||
Reference in New Issue
Block a user