diff --git a/routes/element_types/element_type_delete.py b/routes/element_types/element_type_delete.py index 7b268ba3..1172c0bf 100644 --- a/routes/element_types/element_type_delete.py +++ b/routes/element_types/element_type_delete.py @@ -9,4 +9,4 @@ from core.flask_app import app def element_type_delete(slug, element_type_id): if screens.element_type_usage_count(slug, element_type_id) == 0: screens.delete_element_type(slug, element_type_id) - return redirect(url_for("element_types", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="element_types")) diff --git a/routes/element_types/element_type_edit.py b/routes/element_types/element_type_edit.py index a1ce7b90..75660849 100644 --- a/routes/element_types/element_type_edit.py +++ b/routes/element_types/element_type_edit.py @@ -13,4 +13,4 @@ def element_type_edit(slug, element_type_id): name = request.form.get("name", "").strip() or None bound_definition_id = request.form.get("bound_definition_id") or None screens.update_element_type(slug, element_type_id, name=name, bound_definition_id=bound_definition_id) - return redirect(url_for("element_types", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="element_types")) diff --git a/routes/element_types/element_types.py b/routes/element_types/element_types.py index f00a3c88..f4f9fe6d 100644 --- a/routes/element_types/element_types.py +++ b/routes/element_types/element_types.py @@ -1,6 +1,5 @@ -from flask import render_template, request, redirect, url_for +from flask import request, redirect, url_for -import db import screens from core.flask_app import app @@ -8,18 +7,16 @@ from core.flask_app import app @app.route("/game//element-types", methods=["GET", "POST"]) def element_types(slug): - game = db.game_meta(slug) + """La création/gestion d'un élément de jeu se fait désormais depuis + l'onglet "Éléments de jeu" du tableau de bord (voir game_dashboard.html) + plutôt qu'une page dédiée — cette route ne fait donc plus que traiter la + création (barre de création repliable de cet onglet). Un GET direct + redirige vers le tableau de bord.""" if request.method == "POST": name = request.form.get("name", "").strip() icon = request.form.get("icon", "").strip() or "🧩" bound_definition_id = request.form.get("bound_definition_id") or None if name: screens.create_element_type(slug, name, icon, bound_definition_id) - return redirect(url_for("element_types", slug=slug)) - types = screens.list_element_types(slug) - usage = {t["id"]: screens.element_type_usage_count(slug, t["id"]) for t in types} - definitions = db.list_definitions(slug) - return render_template( - "element_types.html", game=game, types=types, usage=usage, widgets=screens.WIDGETS, - definitions=definitions, - ) + return redirect(url_for("game_dashboard", slug=slug, tab="element_types")) + return redirect(url_for("game_dashboard", slug=slug, tab="element_types")) diff --git a/routes/games/game_dashboard.py b/routes/games/game_dashboard.py index 4cee98ad..886b08a5 100644 --- a/routes/games/game_dashboard.py +++ b/routes/games/game_dashboard.py @@ -24,5 +24,5 @@ def game_dashboard(slug): "game_dashboard.html", game=game, definitions=definitions, screen_list=screen_list, element_types=element_types, element_type_usage=element_type_usage, variables=variables, - variable_types=db.GLOBAL_VARIABLE_TYPES, + variable_types=db.GLOBAL_VARIABLE_TYPES, field_types=db.FIELD_TYPES, ) diff --git a/routes/global_vars/__init__.py b/routes/global_vars/__init__.py index e076e9b1..0da1055e 100644 --- a/routes/global_vars/__init__.py +++ b/routes/global_vars/__init__.py @@ -1,4 +1,3 @@ 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 38e88827..4f50a41f 100644 --- a/routes/global_vars/create_global_var.py +++ b/routes/global_vars/create_global_var.py @@ -15,4 +15,4 @@ def create_global_var(slug): default_value = request.form.get("default_value") or "" if name: db.create_global_variable(slug, name, var_type, default_value) - return redirect(url_for("game_variables", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="variables")) diff --git a/routes/global_vars/game_variables.py b/routes/global_vars/game_variables.py deleted file mode 100644 index 80ae9ff0..00000000 --- a/routes/global_vars/game_variables.py +++ /dev/null @@ -1,19 +0,0 @@ -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 index 4aa8920d..9a89f507 100644 --- a/routes/global_vars/global_var_delete.py +++ b/routes/global_vars/global_var_delete.py @@ -8,4 +8,4 @@ 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)) + return redirect(url_for("game_dashboard", slug=slug, tab="variables")) diff --git a/routes/global_vars/global_var_edit.py b/routes/global_vars/global_var_edit.py index d09ef66f..edf1cd63 100644 --- a/routes/global_vars/global_var_edit.py +++ b/routes/global_vars/global_var_edit.py @@ -10,4 +10,4 @@ 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)) + return redirect(url_for("game_dashboard", slug=slug, tab="variables")) diff --git a/routes/objects/object_new.py b/routes/objects/object_new.py index 6ee7fd29..2c23524a 100644 --- a/routes/objects/object_new.py +++ b/routes/objects/object_new.py @@ -1,4 +1,4 @@ -from flask import render_template, request, redirect, url_for +from flask import request, redirect, url_for import db @@ -8,20 +8,16 @@ from .parse_field_rows import _parse_field_rows @app.route("/game//objects/new", methods=["GET", "POST"]) def object_new(slug): - game = db.game_meta(slug) - existing_definitions = db.list_definitions(slug) - + """La création d'un objet se fait désormais depuis un panneau déplaçable + sur le tableau de bord du jeu (voir game_dashboard.html, formulaire + #objectForm) plutôt qu'une page dédiée — cette route ne fait donc plus + que traiter la soumission de CE formulaire. Un GET direct (ex. lien + externe obsolète) redirige simplement vers le tableau de bord.""" if request.method == "POST": name = request.form.get("object_name", "").strip() fields = _parse_field_rows(request.form) - if name and fields: definition_id = db.create_definition(slug, name, fields) return redirect(url_for("object_view", slug=slug, definition_id=definition_id)) - return render_template( - "object_form.html", - game=game, - existing_definitions=existing_definitions, - field_types=db.FIELD_TYPES, - ) + return redirect(url_for("game_dashboard", slug=slug)) diff --git a/routes/screens/__init__.py b/routes/screens/__init__.py index 5c8c5f25..adfe0646 100644 --- a/routes/screens/__init__.py +++ b/routes/screens/__init__.py @@ -1 +1 @@ -from . import screens_list, screens_new, screen_rename, screen_move, screen_delete, screen_edit, screen_set_aspect +from . import screens_new, screen_rename, screen_move, screen_delete, screen_edit, screen_set_aspect diff --git a/routes/screens/screen_delete.py b/routes/screens/screen_delete.py index 880a5d32..e4c421db 100644 --- a/routes/screens/screen_delete.py +++ b/routes/screens/screen_delete.py @@ -9,6 +9,6 @@ from core.flask_app import app def screen_delete(slug, screen_id): referencing = screens.screens_referencing(slug, screen_id) if referencing: - return redirect(url_for("screens_list", slug=slug, blocked_screen=screen_id)) + return redirect(url_for("game_dashboard", slug=slug, tab="screens", blocked_screen=screen_id)) screens.delete_screen(slug, screen_id) - return redirect(url_for("screens_list", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="screens")) diff --git a/routes/screens/screen_move.py b/routes/screens/screen_move.py index 8a9d920c..24e3b8d2 100644 --- a/routes/screens/screen_move.py +++ b/routes/screens/screen_move.py @@ -9,4 +9,4 @@ from core.flask_app import app def screen_move(slug, screen_id): direction = -1 if request.form.get("direction") == "up" else 1 screens.move_screen(slug, screen_id, direction) - return redirect(url_for("screens_list", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="screens")) diff --git a/routes/screens/screens_list.py b/routes/screens/screens_list.py deleted file mode 100644 index c3e2b093..00000000 --- a/routes/screens/screens_list.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import render_template - -import db -import screens - -from core.flask_app import app - - -@app.route("/game//screens") -def screens_list(slug): - game = db.game_meta(slug) - return render_template("screens_list.html", game=game, screen_list=screens.list_screens(slug)) diff --git a/routes/screens/screens_new.py b/routes/screens/screens_new.py index 4eda95dc..7566471f 100644 --- a/routes/screens/screens_new.py +++ b/routes/screens/screens_new.py @@ -11,4 +11,4 @@ def screens_new(slug): if name: screen_id = screens.create_screen(slug, name) return redirect(url_for("screen_edit", slug=slug, screen_id=screen_id)) - return redirect(url_for("screens_list", slug=slug)) + return redirect(url_for("game_dashboard", slug=slug, tab="screens")) diff --git a/static/style.css b/static/style.css index b92f902f..80dbdd33 100644 --- a/static/style.css +++ b/static/style.css @@ -192,6 +192,7 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d .floatPanel.hidden{ display:none; } .floatPanel--left{ left:24px; width:280px; } .floatPanel--right{ right:24px; width:340px; } +.floatPanel--center{ left:50%; width:560px; max-width:calc(100vw - 48px); transform:translateX(-50%); } .floatPanelHeader{ display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2); diff --git a/templates/base.html b/templates/base.html index b5f0602f..b73a183c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -15,21 +15,17 @@ {% if game is defined and game %} - + {% endif %} diff --git a/templates/element_types.html b/templates/element_types.html deleted file mode 100644 index 6cdf30e2..00000000 --- a/templates/element_types.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "base.html" %} -{% block title %}Éléments de jeu — {{ game.name }}{% endblock %} -{% block content_class %}content-wide content-objectEdit{% endblock %} -{% block body_class %}objectEditBody{% endblock %} -{% block content %} -

Éléments de jeu

-

Un élément de jeu est un conteneur réutilisable.

- -
-
-

Créer

-
-
-
- - -
-
- - -
-
-
- - -
-
-
-
- -
-

Catalogue

- {% if types %} -
- - - - {% for t in types %} - - - - - - {% endfor %} - -
NomUsage
{{ t.icon }} {{ t.name }} - {{ usage.get(t.id, 0) }} instance(s) - {% if t.bound_definition_id %} - {% for d in definitions %}{% if d.id == t.bound_definition_id %} · « {{ d.name }} »{% endif %}{% endfor %} - {% endif %} - - ✏️ -
- -
-
-
- {% else %} -

Aucun élément de jeu défini pour l'instant.

- {% endif %} -
-
-{% endblock %} diff --git a/templates/game_dashboard.html b/templates/game_dashboard.html index 4627b2dc..ee9ed1f5 100644 --- a/templates/game_dashboard.html +++ b/templates/game_dashboard.html @@ -2,8 +2,6 @@ {% block title %}{{ game.name }} — Forge Engine{% endblock %} {% block content_class %}content-wide{% endblock %} {% block content %} -

{{ game.name }}

-

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

+ {% if request.args.get('blocked_screen') %} +
+ ⚠️ Suppression impossible : au moins un autre écran a un bouton qui cible précisément celui-ci. Retire d'abord cette action. +
+ {% endif %}

Écrans du jeu

@@ -63,9 +66,8 @@

Objets

- + Nouvel objet +
-

Un objet a des champs typés (nombre, texte, relation...) — sa création se fait sur sa propre page.

{% if definitions %}
@@ -203,6 +205,104 @@ + + + + + + + {% endblock %} diff --git a/templates/game_variables.html b/templates/game_variables.html deleted file mode 100644 index ff0e0994..00000000 --- a/templates/game_variables.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "base.html" %} -{% block title %}Variables — {{ game.name }}{% endblock %} -{% block content_class %}content-wide content-objectEdit{% endblock %} -{% block body_class %}objectEditBody{% 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).

- -
-
-

Créer

-
-
- - -
-
- - -
-
- - -
-
- -
- -
-

Variables définies

- {% if variables %} - {% for v in variables %} -
- {% endfor %} -
-
- - - {% for v in variables %} - - - - - - - {% endfor %} - -
NomTypeValeur
{{ v.name }} - - - -
- -
-
-
- {% else %} -

Aucune variable pour l'instant — crée la première à gauche.

- {% endif %} -
-
-{% endblock %} diff --git a/templates/object_form.html b/templates/object_form.html deleted file mode 100644 index 4569e23f..00000000 --- a/templates/object_form.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends "base.html" %} -{% block title %}Nouvel objet — {{ game.name }}{% endblock %} -{% block content_class %}content-wide content-objectEdit{% endblock %} -{% block body_class %}objectEditBody{% endblock %} -{% block content %} -

Définir un nouvel objet

- -
-
-
-

Objet

-
-
- - -
-
-
-
- -
-

Champs

- -
-
-
-
-
-
- - - - -{% endblock %} diff --git a/templates/screen_edit.html b/templates/screen_edit.html index 13dd33e0..9632209a 100644 --- a/templates/screen_edit.html +++ b/templates/screen_edit.html @@ -141,7 +141,7 @@ {% endfor %}
{% else %} -

Pas encore d'élément de jeu personnalisé — crées-en un.

+

Pas encore d'élément de jeu personnalisé — crées-en un.

{% endif %} @@ -220,7 +220,7 @@ {% if not global_variables %} -

Aucune variable globale pour l'instant — crées-en une ↗

+

Aucune variable globale pour l'instant — crées-en une ↗

{% endif %} @@ -808,7 +808,7 @@ {% if c.key == 'visibilite_cond_variable' %} -

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

+

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

{% endif %} {% endif %} {% endif %} diff --git a/templates/screens_list.html b/templates/screens_list.html deleted file mode 100644 index 2224d52e..00000000 --- a/templates/screens_list.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "base.html" %} -{% block title %}Écrans — {{ game.name }}{% endblock %} -{% block content_class %}content-wide content-objectEdit{% endblock %} -{% block body_class %}objectEditBody{% endblock %} -{% block content %} -

Écrans du jeu

-{% if request.args.get('blocked_screen') %} -
- ⚠️ Suppression impossible : au moins un autre écran a un bouton qui cible précisément celui-ci. Retire d'abord cette action. -
-{% endif %} - -
-
-

Créer

-
-
- - -
-
-
-
- -
-

Enchaînement des écrans

- {% if screen_list %} -
- - - - {% for s in screen_list %} - - - - - - {% endfor %} - -
#Nom
{{ loop.index }} - {{ s.name }} - {% if loop.first %} ★ Départ{% endif %} - -
-
- ✏️ -
- -
-
-
- {% else %} -

Aucun écran pour l'instant — crée le premier à gauche.

- {% endif %} -
-
-{% endblock %} diff --git a/tests/test_screens_and_elements.py b/tests/test_screens_and_elements.py index e61e4816..42b463fd 100644 --- a/tests/test_screens_and_elements.py +++ b/tests/test_screens_and_elements.py @@ -100,15 +100,17 @@ def test_element_type_template_case_a_and_case_b(client, game, mail_object): follow_redirects=False, ) assert resp.status_code == 302 - html = client.get(f"/game/{slug}/element-types").data.decode() + html = client.get(f"/game/{slug}").data.decode() m = re.search(r"/screens/(\d+)/edit", html) assert m, "le lien vers l'écran-modèle doit apparaître dans le catalogue" template_screen_id = int(m.group(1)) # L'écran-modèle est un écran caché (is_template) : absent de la liste - # normale des écrans jouables. - screens_html = client.get(f"/game/{slug}/screens").data.decode() - assert "CarteMail" not in screens_html or "Modèle" not in screens_html + # normale des écrans jouables (onglet "Écrans du jeu" du tableau de + # bord) — son nom complet ("Modèle : CarteMail") ne doit donc apparaître + # nulle part sur cette page, contrairement à "CarteMail" seul (le nom de + # l'élément de jeu lui-même, affiché dans l'onglet "Éléments de jeu"). + assert "Modèle : CarteMail" not in html # Construit le contenu du modèle : un titre lié au champ "sujet". resp = client.post( @@ -266,7 +268,7 @@ def test_screen_editor_property_panels_closed_by_default(client, game): def test_delete_element_type_removes_hidden_template_screen(client, game): resp = client.post(f"/game/{game}/element-types", data={"name": "ToDelete", "icon": "🧩"}, follow_redirects=False) - html = client.get(f"/game/{game}/element-types").data.decode() + html = client.get(f"/game/{game}").data.decode() m = re.search(r"/screens/(\d+)/edit", html) template_screen_id = int(m.group(1)) element_type_id = int(re.search(r"element-types/(\d+)/delete", html).group(1))