Dev #1
@@ -13,6 +13,15 @@
|
||||
TRIGGER_EVENTS = [
|
||||
("clic", "Au clic"),
|
||||
("soumission", "À la soumission"),
|
||||
# 3.1 (Confort) — interactions au survol, reconstruit comme déclencheur
|
||||
# de flow (au lieu d'un réglage statique dans le panneau de propriétés,
|
||||
# voir universal_controls.py) : "survol" et "fin_survol" sont deux
|
||||
# déclencheurs distincts et explicites (comme "Au clic"), sans effet
|
||||
# implicite — un créateur qui veut qu'un texte affiché au survol
|
||||
# disparaisse ensuite doit poser l'action inverse sur "Fin du survol"
|
||||
# lui-même, plutôt que de compter sur un retour automatique.
|
||||
("survol", "Au survol"),
|
||||
("fin_survol", "Fin du survol"),
|
||||
# Pas de "trigger_element_id" pour celui-ci : il concerne l'ÉCRAN entier,
|
||||
# pas un élément précis (voir 1.2 dans claude/forge-engine-lacunes-boitemail.md
|
||||
# côté projet Forge — "affichage piloté par la donnée"). Se déclenche
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
ELEMENT_ACTION_PROPERTIES = [
|
||||
("visibilite", "Visibilité"),
|
||||
# Voir TRIGGER_EVENTS ("survol"/"fin_survol") : c'est ce qui permet à un
|
||||
# créateur de reconstruire l'ancien comportement "texte affiché au
|
||||
# survol" (et bien d'autres) à la main dans la Logique de la scène —
|
||||
# poser "Modifier un élément → Contenu" sur un déclencheur "Au survol"
|
||||
# d'un AUTRE élément. La valeur est un texte littéral (pas encore de
|
||||
# binding vers un champ d'objet ici — voir applyOpenRowBindings pour le
|
||||
# mécanisme équivalent, propre à "Ouvrir la ligne cliquée").
|
||||
("contenu", "Contenu — texte affiché"),
|
||||
("bg_color", "Couleur de fond"),
|
||||
("border_color", "Couleur de bordure"),
|
||||
("width", "Largeur en %"),
|
||||
|
||||
@@ -2,4 +2,6 @@ TRIGGER_LABELS = {
|
||||
"clic": "Au clic",
|
||||
"soumission": "À la soumission",
|
||||
"affichage": "À l'affichage de l'écran",
|
||||
"survol": "Au survol",
|
||||
"fin_survol": "Fin du survol",
|
||||
}
|
||||
|
||||
@@ -270,6 +270,26 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Déclencheurs "Au survol" / "Fin du survol" — même principe que
|
||||
// bindClicks() (garde-fou anti-doublon compris), mais sur mouseenter/
|
||||
// mouseleave plutôt que click, et les deux événements cherchés
|
||||
// séparément puisque ce sont deux déclencheurs distincts et indépendants
|
||||
// (voir le commentaire sur TRIGGER_EVENTS dans screens/flow/constants.py
|
||||
// — pas de retour automatique à l'état d'avant survol, un créateur qui en
|
||||
// veut un doit poser l'action inverse sur "Fin du survol" lui-même).
|
||||
function bindHoverTriggers() {
|
||||
document.querySelectorAll('[data-element-id]').forEach(elDiv => {
|
||||
if (elDiv.dataset.hoverTriggerBound) return;
|
||||
const elementId = parseInt(elDiv.dataset.elementId, 10);
|
||||
const enterTrigger = findTriggerNode(elementId, 'survol');
|
||||
const leaveTrigger = findTriggerNode(elementId, 'fin_survol');
|
||||
if (!enterTrigger && !leaveTrigger) return;
|
||||
elDiv.dataset.hoverTriggerBound = '1';
|
||||
if (enterTrigger) elDiv.addEventListener('mouseenter', () => runFlowFrom(enterTrigger.id));
|
||||
if (leaveTrigger) elDiv.addEventListener('mouseleave', () => runFlowFrom(leaveTrigger.id));
|
||||
});
|
||||
}
|
||||
|
||||
// Résout, sur l'écran couramment affiché, les placeholders "champ entre
|
||||
// doubles accolades" restés tels quels dans le HTML statique (hors
|
||||
// Répéteur, donc jamais résolus au moment du rendu serveur) avec les
|
||||
@@ -340,6 +360,7 @@
|
||||
applyOpenRowBindings();
|
||||
bindClicks();
|
||||
bindHoverTexts();
|
||||
bindHoverTriggers();
|
||||
// Réévalue aussi les nœuds "À l'affichage de l'écran" de l'écran où
|
||||
// l'on se trouve déjà : une donnée vient de changer (ex: un outil
|
||||
// qu'on vient de débloquer), donc l'affichage doit se mettre à jour
|
||||
@@ -358,6 +379,16 @@
|
||||
};
|
||||
|
||||
function applyElementProperty(targetEl, property, value) {
|
||||
if (property === 'contenu') {
|
||||
// Voir ELEMENT_ACTION_PROPERTIES ("contenu") : texte littéral, pas de
|
||||
// binding vers un champ d'objet ici (contrairement à
|
||||
// applyOpenRowBindings, propre à "Ouvrir la ligne cliquée"). Comme
|
||||
// pour les autres réglages ci-dessous, cible n'importe quel élément —
|
||||
// pointer ceci vers un conteneur remplacerait tout son contenu
|
||||
// imbriqué, à éviter.
|
||||
targetEl.textContent = value == null ? '' : value;
|
||||
return;
|
||||
}
|
||||
if (property === 'surbrillance') {
|
||||
// 3.3 (Confort) — surbrillance générique dynamique : un liseré qui
|
||||
// clignote (voir la classe .forgeHighlight et son animation dans le
|
||||
@@ -603,6 +634,7 @@
|
||||
// directement sur l'écran.
|
||||
bindClicks();
|
||||
bindHoverTexts();
|
||||
bindHoverTriggers();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -420,6 +420,7 @@
|
||||
</select>
|
||||
<input type="color" id="nf-action-element-value-color" style="display:none; margin-top:6px;" value="#5b8cff">
|
||||
<input type="number" id="nf-action-element-value-number" style="display:none; margin-top:6px;" value="50" placeholder="Nouvelle valeur en %">
|
||||
<input type="text" id="nf-action-element-value-text" style="display:none; margin-top:6px;" placeholder="Nouveau texte affiché">
|
||||
|
||||
<label id="nf-action-element-toggle-row" style="display:flex; align-items:center; gap:6px; margin-top:10px; font-size:12px; color:var(--text-dim);">
|
||||
<input type="checkbox" id="nf-action-element-toggle" onchange="toggleFlowElementToggleMode()" style="width:auto; margin:0;">
|
||||
@@ -1325,8 +1326,9 @@ function toggleFlowElementPropertyValue() {
|
||||
var desactiveSel = document.getElementById('nf-action-element-value-desactive');
|
||||
var colorInp = document.getElementById('nf-action-element-value-color');
|
||||
var numInp = document.getElementById('nf-action-element-value-number');
|
||||
var textInp = document.getElementById('nf-action-element-value-text');
|
||||
var toggleRow = document.getElementById('nf-action-element-toggle-row');
|
||||
[visSel, surbrillanceSel, desactiveSel, colorInp, numInp].forEach(function(el){ el.style.display = 'none'; });
|
||||
[visSel, surbrillanceSel, desactiveSel, colorInp, numInp, textInp].forEach(function(el){ el.style.display = 'none'; });
|
||||
if (propSel.value === 'visibilite') {
|
||||
visSel.style.display = 'block';
|
||||
toggleRow.style.display = 'none';
|
||||
@@ -1339,6 +1341,10 @@ function toggleFlowElementPropertyValue() {
|
||||
desactiveSel.style.display = 'block';
|
||||
toggleRow.style.display = 'none';
|
||||
document.getElementById('nf-action-element-toggle').checked = false;
|
||||
} else if (propSel.value === 'contenu') {
|
||||
textInp.style.display = 'block';
|
||||
toggleRow.style.display = 'none';
|
||||
document.getElementById('nf-action-element-toggle').checked = false;
|
||||
} else if (propSel.value === 'bg_color' || propSel.value === 'border_color') {
|
||||
colorInp.style.display = 'block';
|
||||
toggleRow.style.display = 'flex';
|
||||
@@ -1413,6 +1419,8 @@ function submitNodeForm() {
|
||||
payload.element_value = document.getElementById('nf-action-element-value-surbrillance').value;
|
||||
} else if (prop === 'desactive') {
|
||||
payload.element_value = document.getElementById('nf-action-element-value-desactive').value;
|
||||
} else if (prop === 'contenu') {
|
||||
payload.element_value = document.getElementById('nf-action-element-value-text').value;
|
||||
} else {
|
||||
var v1 = isColorProp
|
||||
? document.getElementById('nf-action-element-value-color').value
|
||||
|
||||
+61
-3
@@ -22,9 +22,10 @@ def test_hover_text_control_removed_from_properties_panel(client, game):
|
||||
"""Le réglage "Survol" a été retiré du panneau de propriétés (voir
|
||||
universal_controls.py) : survoler un élément est conceptuellement un
|
||||
déclencheur de la Logique de la scène, pas une propriété statique —
|
||||
il sera réintroduit là-bas. Poster ctrl_survol_texte ne doit donc plus
|
||||
avoir d'effet (le mécanisme data-hover-text/bindHoverTexts sous-jacent
|
||||
reste en place pour un usage futur, voir les tests ci-dessous)."""
|
||||
reconstruit là-bas avec les déclencheurs "survol"/"fin_survol" (voir
|
||||
plus bas). Poster ctrl_survol_texte ne doit donc plus avoir d'effet (le
|
||||
mécanisme data-hover-text/bindHoverTexts sous-jacent reste en place, il
|
||||
n'est simplement plus réglable depuis ce panneau)."""
|
||||
screen_id = _create_screen(client, game)
|
||||
el_id = _add_element(client, game, screen_id, "titre")
|
||||
client.post(f"/game/{game}/elements/{el_id}/save", data={
|
||||
@@ -54,6 +55,63 @@ def test_play_page_exposes_hover_binding_runtime(client, game):
|
||||
assert "bindHoverTexts" in html
|
||||
|
||||
|
||||
def test_survol_trigger_node_persists(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
el_id = _add_element(client, game, screen_id, "titre")
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
|
||||
data=json.dumps({"node_type": "trigger", "trigger_element_id": el_id, "trigger_event": "survol"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["trigger_event"] == "survol"
|
||||
assert node["trigger_element_id"] == el_id
|
||||
|
||||
|
||||
def test_play_page_exposes_hover_trigger_runtime(client, game):
|
||||
"""bindHoverTriggers() (mouseenter/mouseleave -> runFlowFrom) doit être
|
||||
exposé et appelé, exactement comme bindClicks() pour "Au clic"."""
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "bindHoverTriggers" in html
|
||||
assert "'survol'" in html
|
||||
assert "'fin_survol'" in html
|
||||
|
||||
|
||||
def test_modifier_element_contenu_action_persists(client, game):
|
||||
""""Modifier un élément → Contenu" : c'est ce qui permet de reconstruire
|
||||
l'ancien "texte affiché au survol" (et bien d'autres usages) à la main
|
||||
dans la Logique de la scène, en le combinant avec un déclencheur
|
||||
"Au survol" posé sur un AUTRE élément."""
|
||||
screen_id = _create_screen(client, game)
|
||||
source_id = _add_element(client, game, screen_id, "titre")
|
||||
target_id = _add_element(client, game, screen_id, "texte")
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
|
||||
data=json.dumps({
|
||||
"node_type": "action", "action_type": "modifier_element",
|
||||
"target_element_id": target_id, "element_property": "contenu", "element_value": "Survol actif !",
|
||||
}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["element_property"] == "contenu"
|
||||
assert node["element_value"] == "Survol actif !"
|
||||
assert node["target_element_id"] == target_id
|
||||
# source_id n'est utilisé que pour documenter le scénario (le
|
||||
# déclencheur "Au survol" se poserait dessus) — non exercé ici, déjà
|
||||
# couvert par test_survol_trigger_node_persists.
|
||||
assert source_id != target_id
|
||||
|
||||
|
||||
def test_play_page_exposes_contenu_property_runtime(client, game):
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "'contenu'" in html
|
||||
|
||||
|
||||
# ---------- 3.2 — Séquences temporisées ----------
|
||||
|
||||
def test_attendre_action_node_persists_delay(client, game):
|
||||
|
||||
Reference in New Issue
Block a user