From 11a2d397e84a656b49f4b065b333adfe2d2e19ac Mon Sep 17 00:00:00 2001 From: william Date: Tue, 25 Aug 2026 10:00:35 +0200 Subject: [PATCH] =?UTF-8?q?Remplace=20la=20cr=C3=A9ation=20inline=20de=20v?= =?UTF-8?q?ariable=20par=20une=20page=20de=20gestion=20d=C3=A9di=C3=A9e,?= =?UTF-8?q?=20redesign=20du=20tableau=20de=20bord=20du=20jeu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retrait de la création rapide de variable globale depuis le sélecteur de la Condition de visibilité (bouton "+ Créer") : une variable globale est désormais gérée comme un objet "jeu" à part entière, avec une vraie page CRUD ("Variables", nouvelle entrée du menu de gauche) - création, édition du type/valeur, suppression. Le nom reste volontairement immuable après création (c'est par ce nom qu'une condition de visibilité ou une action "Modifier une variable" la référence - la renommer casserait ces réglages en silence), d'où db.update_global_variable qui ne touche que type/valeur. Redesign du tableau de bord du jeu (game_dashboard.html) en deux colonnes : à gauche tout ce qu'on peut créer (écrans, éléments de jeu, variables, jouer, nouvel objet) plus les paramètres du jeu (renommer/ supprimer) ; à droite ce qui a déjà été créé (objets définis). Remplace les cartes Bulma par le système de mise en page compact déjà défini dans style.css (.twoCol/.listRow/.dangerZone/.addBtn) mais jamais utilisé jusqu'ici - plus dense et cohérent avec le reste de l'éditeur. Co-Authored-By: Claude Sonnet 5 --- db/__init__.py | 9 +- db/constants.py | 11 ++ .../delete_global_variable_by_id.py | 14 ++ db/global_vars/update_global_variable.py | 16 +++ routes/global_vars/__init__.py | 3 + routes/global_vars/create_global_var.py | 17 ++- routes/global_vars/game_variables.py | 19 +++ routes/global_vars/global_var_delete.py | 11 ++ routes/global_vars/global_var_edit.py | 13 ++ templates/game_dashboard.html | 130 ++++++++---------- templates/game_variables.html | 66 +++++++++ templates/screen_edit.html | 23 +--- 12 files changed, 227 insertions(+), 105 deletions(-) create mode 100644 db/global_vars/delete_global_variable_by_id.py create mode 100644 db/global_vars/update_global_variable.py create mode 100644 routes/global_vars/game_variables.py create mode 100644 routes/global_vars/global_var_delete.py create mode 100644 routes/global_vars/global_var_edit.py create mode 100644 templates/game_variables.html diff --git a/db/__init__.py b/db/__init__.py index a514da8e..02ce34f0 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -20,7 +20,7 @@ exécute un vrai CREATE TABLE, et remplir le formulaire généré exécute un vrai INSERT dans cette table. """ -from .constants import PROJECTS_DIR, FIELD_TYPES +from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES from .slugify import slugify from .table_name_for import table_name_for from .game_dir import game_dir @@ -56,10 +56,12 @@ from .global_vars.list_global_variables import list_global_variables from .global_vars.get_global_variable import get_global_variable from .global_vars.create_global_variable import create_global_variable from .global_vars.update_global_variable_value import update_global_variable_value +from .global_vars.update_global_variable import update_global_variable from .global_vars.delete_global_variable import delete_global_variable +from .global_vars.delete_global_variable_by_id import delete_global_variable_by_id __all__ = [ - "PROJECTS_DIR", "FIELD_TYPES", + "PROJECTS_DIR", "FIELD_TYPES", "GLOBAL_VARIABLE_TYPES", "slugify", "table_name_for", "game_dir", "db_path", "connect", "list_games", "game_meta", "create_game", "update_game_name", "delete_game", "list_definitions", "get_definition", "create_definition", "rename_definition", @@ -68,5 +70,6 @@ __all__ = [ "list_rows", "relation_options", "insert_row", "get_row", "update_row", "update_row_field", "delete_row", "rows_referencing", "list_global_variables", "get_global_variable", "create_global_variable", - "update_global_variable_value", "delete_global_variable", + "update_global_variable_value", "update_global_variable", "delete_global_variable", + "delete_global_variable_by_id", ] diff --git a/db/constants.py b/db/constants.py index 43b5a49b..184f754c 100644 --- a/db/constants.py +++ b/db/constants.py @@ -14,3 +14,14 @@ FIELD_TYPES = { "date": {"label": "Date", "sql": "TEXT"}, "relation": {"label": "Relation vers un autre objet", "sql": "INTEGER"}, } + +# Types disponibles pour une variable globale (voir db/global_vars/) — un +# sous-ensemble de FIELD_TYPES : ni "relation" (une variable globale n'a pas +# d'objet à pointer) ni "texte_long"/"date" (pas gérés par +# apply_variable_action.py, qui ne coerce que ces 4 types). +GLOBAL_VARIABLE_TYPES = { + "texte": FIELD_TYPES["texte"], + "nombre_entier": FIELD_TYPES["nombre_entier"], + "nombre_decimal": FIELD_TYPES["nombre_decimal"], + "booleen": FIELD_TYPES["booleen"], +} diff --git a/db/global_vars/delete_global_variable_by_id.py b/db/global_vars/delete_global_variable_by_id.py new file mode 100644 index 00000000..8117cbeb --- /dev/null +++ b/db/global_vars/delete_global_variable_by_id.py @@ -0,0 +1,14 @@ +from ..connection import connect +from .ensure_global_vars_schema import ensure_global_vars_schema + + +def delete_global_variable_by_id(slug, var_id): + """Version par id de delete_global_variable — utilisée par la page de + gestion des variables (game_variables.html), où chaque ligne connaît son + id technique mais pas nécessairement un nom encore garanti unique côté + formulaire d'URL (espaces, accents...).""" + ensure_global_vars_schema(slug) + conn = connect(slug) + conn.execute("DELETE FROM _global_variables WHERE id = ?", (var_id,)) + conn.commit() + conn.close() diff --git a/db/global_vars/update_global_variable.py b/db/global_vars/update_global_variable.py new file mode 100644 index 00000000..98c26831 --- /dev/null +++ b/db/global_vars/update_global_variable.py @@ -0,0 +1,16 @@ +from ..connection import connect +from .ensure_global_vars_schema import ensure_global_vars_schema + + +def update_global_variable(slug, var_id, var_type, value): + """Modifie le type et la valeur d'une variable existante depuis la page + de gestion (voir game_variables.html) — le NOM reste volontairement + immuable après création : c'est par ce nom qu'une condition de + visibilité ou une action "Modifier une variable" la référence (voir + get_global_variable.py), la renommer casserait ces réglages en + silence.""" + ensure_global_vars_schema(slug) + conn = connect(slug) + conn.execute("UPDATE _global_variables SET type = ?, value = ? WHERE id = ?", (var_type, str(value), var_id)) + conn.commit() + conn.close() diff --git a/routes/global_vars/__init__.py b/routes/global_vars/__init__.py index 439cf84d..e076e9b1 100644 --- a/routes/global_vars/__init__.py +++ b/routes/global_vars/__init__.py @@ -1 +1,4 @@ from .create_global_var import create_global_var +from .game_variables import game_variables +from .global_var_edit import global_var_edit +from .global_var_delete import global_var_delete diff --git a/routes/global_vars/create_global_var.py b/routes/global_vars/create_global_var.py index 13b4c8f9..38e88827 100644 --- a/routes/global_vars/create_global_var.py +++ b/routes/global_vars/create_global_var.py @@ -1,19 +1,18 @@ -from flask import jsonify, request +from flask import request, redirect, url_for import db from core.flask_app import app -@app.route("/game//global-vars", methods=["POST"]) +@app.route("/game//variables/new", methods=["POST"]) def create_global_var(slug): - """Création rapide d'une variable globale depuis le sélecteur "Variable" - de la Condition de visibilité (voir screen_edit.html, openVisCondVariablePrompt) — - évite d'avoir à quitter le panneau de propriétés pour en créer une.""" + """Depuis la page de gestion des variables (game_variables.html) — + idempotent par nom (voir db.create_global_variable), donc sans risque en + cas de double soumission.""" name = (request.form.get("name") or "").strip() var_type = request.form.get("type") or "texte" default_value = request.form.get("default_value") or "" - if not name: - return jsonify({"ok": False, "error": "Nom manquant"}), 400 - var_id = db.create_global_variable(slug, name, var_type, default_value) - return jsonify({"ok": True, "id": var_id, "name": name}) + if name: + db.create_global_variable(slug, name, var_type, default_value) + return redirect(url_for("game_variables", slug=slug)) diff --git a/routes/global_vars/game_variables.py b/routes/global_vars/game_variables.py new file mode 100644 index 00000000..80ae9ff0 --- /dev/null +++ b/routes/global_vars/game_variables.py @@ -0,0 +1,19 @@ +from flask import render_template + +import db + +from core.flask_app import app + + +@app.route("/game//variables") +def game_variables(slug): + """Page de gestion des variables globales (voir db/global_vars/) : un + objet "jeu" implicite dont les propriétés/valeurs sont visibles partout + (condition de visibilité, action "Modifier une variable"), plutôt + qu'attachées à un objet de données précis.""" + game = db.game_meta(slug) + variables = db.list_global_variables(slug) + return render_template( + "game_variables.html", game=game, variables=variables, + variable_types=db.GLOBAL_VARIABLE_TYPES, + ) diff --git a/routes/global_vars/global_var_delete.py b/routes/global_vars/global_var_delete.py new file mode 100644 index 00000000..4aa8920d --- /dev/null +++ b/routes/global_vars/global_var_delete.py @@ -0,0 +1,11 @@ +from flask import redirect, url_for + +import db + +from core.flask_app import app + + +@app.route("/game//variables//delete", methods=["POST"]) +def global_var_delete(slug, var_id): + db.delete_global_variable_by_id(slug, var_id) + return redirect(url_for("game_variables", slug=slug)) diff --git a/routes/global_vars/global_var_edit.py b/routes/global_vars/global_var_edit.py new file mode 100644 index 00000000..d09ef66f --- /dev/null +++ b/routes/global_vars/global_var_edit.py @@ -0,0 +1,13 @@ +from flask import request, redirect, url_for + +import db + +from core.flask_app import app + + +@app.route("/game//variables//edit", methods=["POST"]) +def global_var_edit(slug, var_id): + var_type = request.form.get("type") or "texte" + value = request.form.get("value") or "" + db.update_global_variable(slug, var_id, var_type, value) + return redirect(url_for("game_variables", slug=slug)) diff --git a/templates/game_dashboard.html b/templates/game_dashboard.html index 438db14d..78e4a055 100644 --- a/templates/game_dashboard.html +++ b/templates/game_dashboard.html @@ -1,81 +1,69 @@ {% extends "base.html" %} {% block title %}{{ game.name }} — Forge Engine{% endblock %} +{% block content_class %}content-wide{% endblock %} {% block breadcrumb %}
  • {{ game.name }}
  • {% endblock %} {% block content %}

    {{ game.name }}

    -

    Base de données : projects/{{ game.slug }}/game.db

    +

    Base de données : projects/{{ game.slug }}/game.db

    - - - -
    -

    ➕ Définir un nouvel objet

    -

    Comme une table de base de données : un nom, et des champs typés

    -
    -
    - -

    Objets définis

    -{% if definitions %} -
    - - - {% for d in definitions %} - - - - - {% endfor %} - -
    - {{ d.name }} -
    table SQL : {{ d.table_name }} -
    - ✏️ -
    -
    -{% else %} -

    Aucun objet défini pour l'instant — un objet, c'est comme une table de base de données : un nom, et des champs typés.

    -{% endif %} - -
    -

    ⚠️ Paramètres du jeu

    -
    -
    - - +
    +
    +

    Créer

    + -
    - + ➕ Définir un nouvel objet + +
    +

    Paramètres du jeu

    + + +
    + + +
    + +
    + +
    - -
    - -
    +
    + +
    +

    Objets définis

    + {% if definitions %} +
    + {% for d in definitions %} + + {% endfor %} +
    + {% else %} +

    Aucun objet défini pour l'instant — un objet, c'est comme une table de base de données : un nom, et des champs typés.

    + {% endif %} +
    {% endblock %} diff --git a/templates/game_variables.html b/templates/game_variables.html new file mode 100644 index 00000000..6a934a46 --- /dev/null +++ b/templates/game_variables.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} +{% block title %}Variables — {{ game.name }}{% endblock %} +{% block content_class %}content-wide{% endblock %} +{% block breadcrumb %} +
  • {{ game.name }}
  • +
  • Variables
  • +{% endblock %} +{% block content %} +

    Variables globales

    +

    Des propriétés/valeurs communes à tout le jeu (pas attachées à un objet de données précis) — utile par exemple pour une porte ouverte ou non, un niveau débloqué... Une variable peut être lue dans une "Condition de visibilité" (propriétés d'un élément) et modifiée par une action "Modifier une variable" (Logique de la scène).

    + +
    +

    Nouvelle variable

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +

    Variables définies

    +{% if variables %} +
    + {% for v in variables %} +
    +
    +
    + +
    + + + +
    + +
    +
    +
    +
    + {% endfor %} +
    +{% else %} +

    Aucune variable pour l'instant — crée la première ci-dessus.

    +{% endif %} +{% endblock %} diff --git a/templates/screen_edit.html b/templates/screen_edit.html index b6bbf6e2..13d69615 100644 --- a/templates/screen_edit.html +++ b/templates/screen_edit.html @@ -449,7 +449,7 @@
    {% if c.key == 'visibilite_cond_variable' %} - +

    Pas la variable qu'il te faut ? Gérer les variables du jeu ↗

    {% endif %} {% endif %} {% endif %} @@ -1530,27 +1530,6 @@ function bindJaugeDefinitionSelect() { // globale, ou champ d'un objet) — les deux autres restent en base (peu // importe, seul le mode courant est lu par is_element_visible()) mais sont // masqués pour ne pas embrouiller. ---------- -function createVisCondVariable() { - var name = window.prompt("Nom de la nouvelle variable globale (ex. niveau_debloque) :"); - if (!name || !name.trim()) return; - var body = new URLSearchParams({ name: name.trim(), type: 'texte', default_value: '' }); - fetch("{{ url_for('create_global_var', slug=game.slug) }}", { method: 'POST', body: body }) - .then(function (r) { return r.json(); }) - .then(function (json) { - if (!json.ok) { window.alert(json.error || "Échec de la création."); return; } - var sel = document.getElementById('field-visibilite_cond_variable'); - if (!sel) return; - if (!sel.querySelector('option[value="' + CSS.escape(json.name) + '"]')) { - var opt = document.createElement('option'); - opt.value = json.name; - opt.textContent = json.name; - sel.appendChild(opt); - } - sel.value = json.name; - submitPropsForm(); - }); -} - function toggleVisCondFields(sel) { var mode = sel.value; document.querySelectorAll('.visCondRow').forEach(function (row) {