first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
"""Logique visuelle (éditeur de flow à nœuds) : déclencheurs, conditions,
|
||||
actions, sous forme de graphe propre à chaque écran."""
|
||||
@@ -0,0 +1,13 @@
|
||||
import db
|
||||
|
||||
|
||||
def add_flow_edge(slug, screen_id, from_node_id, from_port, to_node_id):
|
||||
conn = db.connect(slug)
|
||||
conn.execute(
|
||||
"INSERT INTO _flow_edges (screen_id, from_node_id, from_port, to_node_id) VALUES (?, ?, ?, ?)",
|
||||
(screen_id, from_node_id, from_port or "out", to_node_id),
|
||||
)
|
||||
edge_id = conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return edge_id
|
||||
@@ -0,0 +1,21 @@
|
||||
import db
|
||||
|
||||
from .ensure_flow_schema import ensure_flow_schema
|
||||
from .constants import FLOW_NODE_FIELDS
|
||||
|
||||
|
||||
def add_flow_node(slug, screen_id, node_type, x=40, y=40, **fields):
|
||||
ensure_flow_schema(slug)
|
||||
cols = ["screen_id", "node_type", "x", "y"]
|
||||
vals = [screen_id, node_type, x, y]
|
||||
for k, v in fields.items():
|
||||
if k in FLOW_NODE_FIELDS and v not in (None, ""):
|
||||
cols.append(k)
|
||||
vals.append(v)
|
||||
placeholders = ", ".join("?" for _ in cols)
|
||||
conn = db.connect(slug)
|
||||
conn.execute(f"INSERT INTO _flow_nodes ({', '.join(cols)}) VALUES ({placeholders})", vals)
|
||||
node_id = conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return node_id
|
||||
@@ -0,0 +1,47 @@
|
||||
# ---------- Logique visuelle (éditeur de flow à nœuds) ----------
|
||||
#
|
||||
# Remplace l'ancien système "Actions" attaché à chaque élément : toute la
|
||||
# logique d'une scène (déclencheurs, conditions, actions) vit maintenant dans
|
||||
# un graphe de nœuds propre à l'écran, visible dans le panneau "Logique de
|
||||
# la scène" de l'éditeur (voir screen_edit.html) — un nœud Déclencheur
|
||||
# ("Au clic sur X") relié à des nœuds Condition (deux sorties Vrai/Faux) et
|
||||
# Action (les mêmes effets qu'avant : changer d'écran, modifier un élément,
|
||||
# modifier une donnée). L'ancien système _actions reste en base pour ne rien
|
||||
# casser sur les jeux déjà créés, mais n'est plus exposé dans l'éditeur ni
|
||||
# utilisé par le mode jouable.
|
||||
|
||||
TRIGGER_EVENTS = [
|
||||
("clic", "Au clic"),
|
||||
("soumission", "À la soumission"),
|
||||
# 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
|
||||
# côté jouable à chaque fois que l'écran est montré (premier affichage,
|
||||
# retour en arrière, changement d'écran...) ET après toute donnée
|
||||
# modifiée pendant qu'on est déjà sur cet écran — ce qui permet de
|
||||
# cacher/montrer un élément selon l'état de la partie SANS qu'un clic
|
||||
# explicite soit nécessaire pour le réévaluer.
|
||||
("affichage", "À l'affichage de l'écran"),
|
||||
]
|
||||
|
||||
CONDITION_OPERATORS = [
|
||||
("egal", "est égal à"),
|
||||
("different", "est différent de"),
|
||||
("superieur", "est supérieur à"),
|
||||
("inferieur", "est inférieur à"),
|
||||
("superieur_egal", "est supérieur ou égal à"),
|
||||
("inferieur_egal", "est inférieur ou égal à"),
|
||||
]
|
||||
CONDITION_OPERATOR_LABELS = dict(CONDITION_OPERATORS)
|
||||
|
||||
FLOW_NODE_FIELDS = {
|
||||
"trigger_element_id", "trigger_event",
|
||||
"cond_definition_id", "cond_row_id", "cond_field", "cond_field_type", "cond_operator", "cond_value",
|
||||
# 2.4 — conditions combinées (ET/OU) : cond_clauses est une liste JSON de
|
||||
# clauses supplémentaires (en plus de la clause "historique" ci-dessus,
|
||||
# qui reste la première clause) + cond_combinator ('et'/'ou') pour savoir
|
||||
# comment les combiner. Absents => comportement legacy (une seule clause).
|
||||
"cond_clauses", "cond_combinator",
|
||||
"action_type", "target_screen_id", "target_element_id", "element_property", "element_value",
|
||||
"target_definition_id", "target_row_id", "target_field", "data_operation", "data_value",
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import db
|
||||
|
||||
|
||||
def delete_flow_edge(slug, edge_id):
|
||||
conn = db.connect(slug)
|
||||
conn.execute("DELETE FROM _flow_edges WHERE id = ?", (edge_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -0,0 +1,9 @@
|
||||
import db
|
||||
|
||||
|
||||
def delete_flow_node(slug, node_id):
|
||||
conn = db.connect(slug)
|
||||
conn.execute("DELETE FROM _flow_edges WHERE from_node_id = ? OR to_node_id = ?", (node_id, node_id))
|
||||
conn.execute("DELETE FROM _flow_nodes WHERE id = ?", (node_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -0,0 +1,55 @@
|
||||
import db
|
||||
|
||||
|
||||
def ensure_flow_schema(slug):
|
||||
conn = db.connect(slug)
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS _flow_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE,
|
||||
node_type TEXT NOT NULL,
|
||||
x REAL NOT NULL DEFAULT 40,
|
||||
y REAL NOT NULL DEFAULT 40,
|
||||
trigger_element_id INTEGER REFERENCES _screen_elements(id),
|
||||
trigger_event TEXT,
|
||||
cond_definition_id INTEGER,
|
||||
cond_row_id INTEGER,
|
||||
cond_field TEXT,
|
||||
cond_field_type TEXT,
|
||||
cond_operator TEXT,
|
||||
cond_value TEXT,
|
||||
action_type TEXT,
|
||||
target_screen_id INTEGER REFERENCES _screens(id),
|
||||
target_element_id INTEGER REFERENCES _screen_elements(id),
|
||||
element_property TEXT,
|
||||
element_value TEXT,
|
||||
target_definition_id INTEGER,
|
||||
target_row_id INTEGER,
|
||||
target_field TEXT,
|
||||
data_operation TEXT,
|
||||
data_value TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS _flow_edges (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE,
|
||||
from_node_id INTEGER NOT NULL REFERENCES _flow_nodes(id) ON DELETE CASCADE,
|
||||
from_port TEXT NOT NULL DEFAULT 'out',
|
||||
to_node_id INTEGER NOT NULL REFERENCES _flow_nodes(id) ON DELETE CASCADE
|
||||
);
|
||||
"""
|
||||
)
|
||||
# 2.4 — conditions combinées (ET/OU) : un nœud Condition peut tester
|
||||
# PLUSIEURS champs à la fois plutôt qu'un seul. cond_clauses (JSON, une
|
||||
# liste de clauses en plus de la clause "historique" cond_definition_id/
|
||||
# cond_field/etc.) + cond_combinator ('et'/'ou') sont ajoutés en ALTER
|
||||
# TABLE pour ne rien casser sur les jeux déjà créés — un nœud sans
|
||||
# cond_clauses garde exactement son comportement d'avant (une seule
|
||||
# comparaison), voir evaluateConditionNode() dans templates/play.html.
|
||||
cols = {r["name"] for r in conn.execute("PRAGMA table_info(_flow_nodes)").fetchall()}
|
||||
if "cond_clauses" not in cols:
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_clauses TEXT")
|
||||
if "cond_combinator" not in cols:
|
||||
conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_combinator TEXT NOT NULL DEFAULT 'et'")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -0,0 +1,8 @@
|
||||
import db
|
||||
|
||||
|
||||
def get_flow_node(slug, node_id):
|
||||
conn = db.connect(slug)
|
||||
row = conn.execute("SELECT * FROM _flow_nodes WHERE id = ?", (node_id,)).fetchone()
|
||||
conn.close()
|
||||
return dict(row) if row else None
|
||||
@@ -0,0 +1,11 @@
|
||||
import db
|
||||
|
||||
from .ensure_flow_schema import ensure_flow_schema
|
||||
|
||||
|
||||
def list_flow_edges(slug, screen_id):
|
||||
ensure_flow_schema(slug)
|
||||
conn = db.connect(slug)
|
||||
rows = conn.execute("SELECT * FROM _flow_edges WHERE screen_id = ? ORDER BY id", (screen_id,)).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
@@ -0,0 +1,11 @@
|
||||
import db
|
||||
|
||||
from .ensure_flow_schema import ensure_flow_schema
|
||||
|
||||
|
||||
def list_flow_nodes(slug, screen_id):
|
||||
ensure_flow_schema(slug)
|
||||
conn = db.connect(slug)
|
||||
rows = conn.execute("SELECT * FROM _flow_nodes WHERE screen_id = ? ORDER BY id", (screen_id,)).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
@@ -0,0 +1,8 @@
|
||||
import db
|
||||
|
||||
|
||||
def move_flow_node(slug, node_id, x, y):
|
||||
conn = db.connect(slug)
|
||||
conn.execute("UPDATE _flow_nodes SET x = ?, y = ? WHERE id = ?", (x, y, node_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user