Dev #2

Merged
w-vandal merged 24 commits from dev into main 2026-08-27 13:00:44 +00:00
2 changed files with 172 additions and 3 deletions
Showing only changes of commit 1b24df9546 - Show all commits
+6
View File
@@ -552,3 +552,9 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p
.columnFilterMenu.hidden{ display:none; }
.columnFilterOption{ display:flex; align-items:center; gap:6px; padding:4px 6px; border-radius:4px; font-size:12.5px; white-space:nowrap; }
.columnFilterOption:hover{ background:var(--panel2); }
/* ---------- Panneau "Configurer la valeur" (variable Objet/Tableau) ---------- */
/* La colonne "Clé" n'a de sens que pour un Objet (paires clé/valeur) — un
Tableau n'est qu'une liste de valeurs, voir openVarValuePanel() dans
game_dashboard.html. */
#varValuePanel[data-mode="tableau"] .varValueKeyField{ display:none; }
+166 -3
View File
@@ -146,12 +146,13 @@
</div>
<form method="post" action="{{ url_for('create_global_var', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-variables" style="display:none;">
<input type="text" name="name" placeholder="Nom (ex. porte_ouverte)" required autocomplete="off">
<select name="type">
<select id="newVarType" name="type" onchange="onVarTypeSelectChange(this)">
{% for key, val in variable_types.items() %}
<option value="{{ key }}">{{ val.label }}</option>
{% endfor %}
</select>
<textarea name="default_value" placeholder="Valeur de départ (JSON pour Objet/Tableau, ex. {} ou [])" rows="1"></textarea>
<input type="text" id="newVarValue" name="default_value" placeholder="Valeur de départ">
<button type="button" id="newVarConfigureBtn" class="button is-small" style="display:none;" onclick="openVarValuePanel('newVarValue', document.getElementById('newVarType').value)">🔧 Configurer la valeur</button>
<button type="submit" class="primary">Créer</button>
</form>
{% if variables %}
@@ -174,7 +175,10 @@
</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>
<div class="fieldRow" style="align-items:center;">
<input type="text" form="varEditForm{{ v.id }}" name="value" id="varValue-{{ v.id }}" value="{{ v.value or '' }}" readonly aria-label="Valeur de « {{ v.name }} »">
<button type="button" class="button is-small" onclick="openVarValuePanel('varValue-{{ v.id }}', '{{ v.type }}')">🔧 Configurer</button>
</div>
{% else %}
<input type="text" form="varEditForm{{ v.id }}" name="value" value="{{ v.value or '' }}" aria-label="Valeur de « {{ v.name }} »">
{% endif %}
@@ -196,6 +200,56 @@
{% endif %}
</div>
<!-- ---------- Panneau "Configurer la valeur" (variable Objet/Tableau) ---------- -->
<!-- Un seul panneau partagé (pas un par variable) : une seule valeur à la
fois peut être en cours d'édition. openVarValuePanel(targetInputId,
mode) mémorise vers quel <input> écrire le JSON reconstruit (celui de
la barre de création, ou celui, en lecture seule, de la ligne d'une
variable existante) — pas de JSON à taper à la main : une ligne par
clé (Objet) ou par élément (Tableau), avec un type (Texte/Nombre/
Oui-Non) qui pilote comment la valeur est encodée en JSON. Ne gère
que des valeurs SIMPLES (pas d'objet/tableau imbriqué) — largement
suffisant pour une configuration ou une liste, et ça reste lisible
pour qui ne connaît pas JSON. -->
<div class="floatPanel floatPanel--center hidden" id="varValuePanel">
<div class="floatPanelHeader" id="varValuePanelHeader">
<span class="floatPanelTitle" id="varValuePanelTitle">⋮⋮ Configurer la valeur</span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeVarValuePanel()">✕</button>
</div>
<div class="floatPanelBody">
<p class="hint mb-2" id="varValuePanelHint"></p>
<button type="button" class="button is-outlined is-fullwidth mb-2" onclick="addVarValueRow()">+ Ajouter</button>
<div id="varValueRows"></div>
<div class="actionsRow"><button type="button" class="primary" onclick="applyVarValuePanel()">OK — utiliser cette valeur</button></div>
</div>
<div class="floatPanelResizeHandle" id="varValuePanelResizeHandle"></div>
</div>
<template id="varValueRowTemplate">
<div class="fieldRowCard box varValueRow">
<div class="fieldRow">
<div class="formField varValueKeyField">
<label>Clé</label>
<input type="text" class="varValueKey" placeholder="ex. nom, degats...">
</div>
<div class="formField">
<label>Type</label>
<select class="varValueType">
<option value="texte">Texte</option>
<option value="nombre">Nombre</option>
<option value="booleen">Oui / Non</option>
</select>
</div>
<div class="formField" style="flex:2;">
<label>Valeur</label>
<input type="text" class="varValueValue" placeholder="ex. Alice, 12, Oui...">
</div>
</div>
<button type="button" class="button is-small is-danger varValueRemoveBtn">🗑️ Retirer</button>
</div>
</template>
<div class="dangerZone dashDangerZone">
<h3>Paramètres du jeu</h3>
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}">
@@ -651,6 +705,19 @@ function toggleDashCreate(tab) {
}
}
// Barre "+ Créer une variable" : pour un type Objet/Tableau, la valeur de
// départ se configure via le panneau à champs (voir openVarValuePanel) —
// la saisie libre n'a alors plus de sens, remplacée par un aperçu en
// lecture seule + le bouton "🔧 Configurer".
function onVarTypeSelectChange(sel) {
var valueInput = document.getElementById('newVarValue');
var configureBtn = document.getElementById('newVarConfigureBtn');
var structured = sel.value === 'objet' || sel.value === 'tableau';
valueInput.readOnly = structured;
configureBtn.style.display = structured ? '' : 'none';
if (structured && !valueInput.value) valueInput.value = sel.value === 'objet' ? '{}' : '[]';
}
// Onglet initial : ?tab=... (utilisé par les liens "crée-en un" ailleurs
// dans l'outil, ex. l'éditeur d'écran, pour arriver directement sur le bon
// onglet plutôt que de laisser deviner où cliquer).
@@ -942,5 +1009,101 @@ function switchObjectPanelTab(btn, tab) {
p.classList.toggle('active', p.dataset.optabPanel === tab);
});
}
// ---------- Panneau "Configurer la valeur" (variable Objet/Tableau) ----------
// Un seul panneau partagé (voir le commentaire sur son HTML) : à
// l'ouverture, mémorise vers quel <input> (targetId) le JSON reconstruit
// devra être réécrit, construit une ligne par clé/élément déjà présent
// dans le JSON actuel de cet <input>, puis "OK" sérialise les lignes et
// referme.
(function () {
var panel = document.getElementById('varValuePanel');
var rowsWrap = document.getElementById('varValueRows');
var rowTemplate = document.getElementById('varValueRowTemplate');
var title = document.getElementById('varValuePanelTitle');
var hint = document.getElementById('varValuePanelHint');
makeFloatPanelDraggable(panel, document.getElementById('varValuePanelHeader'));
makeFloatPanelResizable(panel, document.getElementById('varValuePanelResizeHandle'));
makeFloatPanelFullscreenable(panel, panel.querySelector('.floatPanelFullscreenBtn'));
function valueTypeOf(v) {
if (typeof v === 'boolean') return 'booleen';
if (typeof v === 'number') return 'nombre';
return 'texte'; // texte, et repli pour un objet/tableau imbriqué (non éditable ici, voir addRowFrom)
}
function valueTextOf(v, type) {
if (type === 'booleen') return v ? 'Oui' : 'Non';
if (typeof v === 'object' && v !== null) return JSON.stringify(v); // imbriqué : préservé tel quel, en texte opaque
return v == null ? '' : String(v);
}
function coerceRowValue(type, text) {
if (type === 'nombre') { var n = parseFloat(text); return isNaN(n) ? 0 : n; }
if (type === 'booleen') return ['oui', 'vrai', '1', 'true'].indexOf(text.trim().toLowerCase()) !== -1;
return text;
}
function addRowFrom(key, value) {
var clone = rowTemplate.content.cloneNode(true);
var row = clone.querySelector('.varValueRow');
var type = valueTypeOf(value);
if (key !== null) row.querySelector('.varValueKey').value = key;
row.querySelector('.varValueType').value = type;
row.querySelector('.varValueValue').value = valueTextOf(value, type);
row.querySelector('.varValueRemoveBtn').addEventListener('click', function () { row.remove(); });
rowsWrap.appendChild(clone);
}
window.addVarValueRow = function () {
addRowFrom(panel.dataset.mode === 'tableau' ? null : '', '');
};
window.openVarValuePanel = function (targetInputId, mode) {
var input = document.getElementById(targetInputId);
if (!input) return;
panel.dataset.target = targetInputId;
panel.dataset.mode = mode;
rowsWrap.innerHTML = '';
var parsed;
try { parsed = JSON.parse(input.value || (mode === 'objet' ? '{}' : '[]')); } catch (e) { parsed = mode === 'objet' ? {} : []; }
if (mode === 'tableau') {
title.textContent = '⋮⋮ Configurer le tableau';
hint.textContent = 'Une ligne par élément — valeurs simples uniquement (pas de tableau/objet imbriqué).';
(Array.isArray(parsed) ? parsed : []).forEach(function (v) { addRowFrom(null, v); });
} else {
title.textContent = '⋮⋮ Configurer l\'objet';
hint.textContent = 'Une ligne par clé — valeurs simples uniquement (pas de tableau/objet imbriqué).';
Object.keys(parsed || {}).forEach(function (k) { addRowFrom(k, parsed[k]); });
}
if (!rowsWrap.children.length) window.addVarValueRow();
panel.classList.remove('hidden');
panel._fsToggle.enter();
};
window.closeVarValuePanel = function () {
panel.classList.add('hidden');
};
window.applyVarValuePanel = function () {
var mode = panel.dataset.mode;
var result = mode === 'tableau' ? [] : {};
rowsWrap.querySelectorAll('.varValueRow').forEach(function (row) {
var type = row.querySelector('.varValueType').value;
var value = coerceRowValue(type, row.querySelector('.varValueValue').value);
if (mode === 'tableau') {
result.push(value);
} else {
var key = row.querySelector('.varValueKey').value.trim();
if (key) result[key] = value;
}
});
var input = document.getElementById(panel.dataset.target);
if (input) {
input.value = JSON.stringify(result);
input.dispatchEvent(new Event('input', { bubbles: true }));
}
closeVarValuePanel();
};
})();
</script>
{% endblock %}