Fusionne les 4 pages de création dans le dashboard, retire le titre/BDD
Suite de la demande précédente : les 4 pages autrefois listées dans la barre de navigation n'existent plus en tant que pages séparées — tout vit désormais dans les onglets du tableau de bord (commit précédent) ou, pour la création d'un objet, dans un panneau déplaçable. - screens_list.html + sa route (screens_list) : supprimés (l'onglet "Écrans du jeu" du dashboard couvre déjà tout : créer, réordonner, éditer, supprimer). screen_new/screen_move/screen_delete redirigent maintenant vers le dashboard (tab=screens) au lieu de cette page. - element_types.html : supprimé, mais la route element_types est conservée (GET redirige vers le dashboard, POST — utilisé par la barre de création repliable de l'onglet "Éléments de jeu" — continue de fonctionner). element_type_edit/element_type_delete redirigent aussi vers le dashboard. - game_variables.html + sa route (game_variables) : supprimés (l'onglet "Variables" du dashboard couvre déjà tout). create_global_var/ global_var_edit/global_var_delete redirigent vers le dashboard (tab=variables) au lieu de cette page. - object_form.html : supprimé. La route object_new (POST) est conservée pour traiter la soumission du panneau — voir plus bas — mais ne rend plus de page pour un GET (redirige vers le dashboard). Nouveau panneau déplaçable "Nouvel objet" sur le dashboard (bouton "+ Nouvel objet" de l'onglet Objets) : réutilise .floatPanel/ .floatPanelHeader/.floatPanelBody (déjà utilisées dans l'éditeur d'écran) avec une nouvelle variante centrée (.floatPanel--center) et son propre glisser-déposer minimal (pas de position persistée, contrairement aux panneaux de l'éditeur d'écran — inutile pour un panneau ouvert ponctuellement). Contenu et script (object_form.js) repris tels quels de l'ancienne page. base.html : la barre de navigation du jeu n'a donc plus que 2 liens — "📊 Tableau de bord" (nouveau) et "▶️ Jouer" (toujours en dernier). game_dashboard.html : titre du jeu et chemin de la base de données retirés (redondants avec le nom déjà visible dans l'onglet du navigateur/la barre de nav). Les liens "crée-en un"/"gérer les variables" dans l'éditeur d'écran (screen_edit.html) pointent maintenant vers le dashboard avec le bon onglet (?tab=...), lu et appliqué au chargement de la page (switchDashTab() côté JS). 2 tests (test_screens_and_elements.py) mis à jour : ils vérifiaient le contenu des pages supprimées (element-types, screens) — adaptés pour vérifier la même chose sur le dashboard, qui porte maintenant cette information. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
65450a5719
commit
d875557254
@@ -9,4 +9,4 @@ from core.flask_app import app
|
|||||||
def element_type_delete(slug, element_type_id):
|
def element_type_delete(slug, element_type_id):
|
||||||
if screens.element_type_usage_count(slug, element_type_id) == 0:
|
if screens.element_type_usage_count(slug, element_type_id) == 0:
|
||||||
screens.delete_element_type(slug, element_type_id)
|
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"))
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ def element_type_edit(slug, element_type_id):
|
|||||||
name = request.form.get("name", "").strip() or None
|
name = request.form.get("name", "").strip() or None
|
||||||
bound_definition_id = request.form.get("bound_definition_id") 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)
|
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"))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from flask import render_template, request, redirect, url_for
|
from flask import request, redirect, url_for
|
||||||
|
|
||||||
import db
|
|
||||||
import screens
|
import screens
|
||||||
|
|
||||||
from core.flask_app import app
|
from core.flask_app import app
|
||||||
@@ -8,18 +7,16 @@ from core.flask_app import app
|
|||||||
|
|
||||||
@app.route("/game/<slug>/element-types", methods=["GET", "POST"])
|
@app.route("/game/<slug>/element-types", methods=["GET", "POST"])
|
||||||
def element_types(slug):
|
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":
|
if request.method == "POST":
|
||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
icon = request.form.get("icon", "").strip() or "🧩"
|
icon = request.form.get("icon", "").strip() or "🧩"
|
||||||
bound_definition_id = request.form.get("bound_definition_id") or None
|
bound_definition_id = request.form.get("bound_definition_id") or None
|
||||||
if name:
|
if name:
|
||||||
screens.create_element_type(slug, name, icon, bound_definition_id)
|
screens.create_element_type(slug, name, icon, bound_definition_id)
|
||||||
return redirect(url_for("element_types", slug=slug))
|
return redirect(url_for("game_dashboard", slug=slug, tab="element_types"))
|
||||||
types = screens.list_element_types(slug)
|
return redirect(url_for("game_dashboard", slug=slug, tab="element_types"))
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -24,5 +24,5 @@ def game_dashboard(slug):
|
|||||||
"game_dashboard.html", game=game, definitions=definitions,
|
"game_dashboard.html", game=game, definitions=definitions,
|
||||||
screen_list=screen_list, element_types=element_types,
|
screen_list=screen_list, element_types=element_types,
|
||||||
element_type_usage=element_type_usage, variables=variables,
|
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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from .create_global_var import create_global_var
|
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_edit import global_var_edit
|
||||||
from .global_var_delete import global_var_delete
|
from .global_var_delete import global_var_delete
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ def create_global_var(slug):
|
|||||||
default_value = request.form.get("default_value") or ""
|
default_value = request.form.get("default_value") or ""
|
||||||
if name:
|
if name:
|
||||||
db.create_global_variable(slug, name, var_type, default_value)
|
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"))
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
from flask import render_template
|
|
||||||
|
|
||||||
import db
|
|
||||||
|
|
||||||
from core.flask_app import app
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/game/<slug>/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,
|
|
||||||
)
|
|
||||||
@@ -8,4 +8,4 @@ from core.flask_app import app
|
|||||||
@app.route("/game/<slug>/variables/<int:var_id>/delete", methods=["POST"])
|
@app.route("/game/<slug>/variables/<int:var_id>/delete", methods=["POST"])
|
||||||
def global_var_delete(slug, var_id):
|
def global_var_delete(slug, var_id):
|
||||||
db.delete_global_variable_by_id(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"))
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ def global_var_edit(slug, var_id):
|
|||||||
var_type = request.form.get("type") or "texte"
|
var_type = request.form.get("type") or "texte"
|
||||||
value = request.form.get("value") or ""
|
value = request.form.get("value") or ""
|
||||||
db.update_global_variable(slug, var_id, var_type, value)
|
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"))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import render_template, request, redirect, url_for
|
from flask import request, redirect, url_for
|
||||||
|
|
||||||
import db
|
import db
|
||||||
|
|
||||||
@@ -8,20 +8,16 @@ from .parse_field_rows import _parse_field_rows
|
|||||||
|
|
||||||
@app.route("/game/<slug>/objects/new", methods=["GET", "POST"])
|
@app.route("/game/<slug>/objects/new", methods=["GET", "POST"])
|
||||||
def object_new(slug):
|
def object_new(slug):
|
||||||
game = db.game_meta(slug)
|
"""La création d'un objet se fait désormais depuis un panneau déplaçable
|
||||||
existing_definitions = db.list_definitions(slug)
|
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":
|
if request.method == "POST":
|
||||||
name = request.form.get("object_name", "").strip()
|
name = request.form.get("object_name", "").strip()
|
||||||
fields = _parse_field_rows(request.form)
|
fields = _parse_field_rows(request.form)
|
||||||
|
|
||||||
if name and fields:
|
if name and fields:
|
||||||
definition_id = db.create_definition(slug, name, fields)
|
definition_id = db.create_definition(slug, name, fields)
|
||||||
return redirect(url_for("object_view", slug=slug, definition_id=definition_id))
|
return redirect(url_for("object_view", slug=slug, definition_id=definition_id))
|
||||||
|
|
||||||
return render_template(
|
return redirect(url_for("game_dashboard", slug=slug))
|
||||||
"object_form.html",
|
|
||||||
game=game,
|
|
||||||
existing_definitions=existing_definitions,
|
|
||||||
field_types=db.FIELD_TYPES,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ from core.flask_app import app
|
|||||||
def screen_delete(slug, screen_id):
|
def screen_delete(slug, screen_id):
|
||||||
referencing = screens.screens_referencing(slug, screen_id)
|
referencing = screens.screens_referencing(slug, screen_id)
|
||||||
if referencing:
|
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)
|
screens.delete_screen(slug, screen_id)
|
||||||
return redirect(url_for("screens_list", slug=slug))
|
return redirect(url_for("game_dashboard", slug=slug, tab="screens"))
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ from core.flask_app import app
|
|||||||
def screen_move(slug, screen_id):
|
def screen_move(slug, screen_id):
|
||||||
direction = -1 if request.form.get("direction") == "up" else 1
|
direction = -1 if request.form.get("direction") == "up" else 1
|
||||||
screens.move_screen(slug, screen_id, direction)
|
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"))
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
from flask import render_template
|
|
||||||
|
|
||||||
import db
|
|
||||||
import screens
|
|
||||||
|
|
||||||
from core.flask_app import app
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/game/<slug>/screens")
|
|
||||||
def screens_list(slug):
|
|
||||||
game = db.game_meta(slug)
|
|
||||||
return render_template("screens_list.html", game=game, screen_list=screens.list_screens(slug))
|
|
||||||
@@ -11,4 +11,4 @@ def screens_new(slug):
|
|||||||
if name:
|
if name:
|
||||||
screen_id = screens.create_screen(slug, name)
|
screen_id = screens.create_screen(slug, name)
|
||||||
return redirect(url_for("screen_edit", slug=slug, screen_id=screen_id))
|
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"))
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
|
|||||||
.floatPanel.hidden{ display:none; }
|
.floatPanel.hidden{ display:none; }
|
||||||
.floatPanel--left{ left:24px; width:280px; }
|
.floatPanel--left{ left:24px; width:280px; }
|
||||||
.floatPanel--right{ right:24px; width:340px; }
|
.floatPanel--right{ right:24px; width:340px; }
|
||||||
|
.floatPanel--center{ left:50%; width:560px; max-width:calc(100vw - 48px); transform:translateX(-50%); }
|
||||||
.floatPanelHeader{
|
.floatPanelHeader{
|
||||||
display:flex; align-items:center; justify-content:space-between; gap:8px;
|
display:flex; align-items:center; justify-content:space-between; gap:8px;
|
||||||
padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2);
|
padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2);
|
||||||
|
|||||||
+10
-14
@@ -15,21 +15,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% if game is defined and game %}
|
{% if game is defined and game %}
|
||||||
<!-- Barre toujours visible entre les différents éditeurs d'UN MÊME jeu —
|
<!-- Barre toujours visible entre les différentes pages d'UN MÊME jeu.
|
||||||
sans elle, changer d'éditeur (écrans, éléments de jeu, variables,
|
Écrans/Éléments de jeu/Variables/Objets n'ont plus leur propre page —
|
||||||
objets) obligeait à repasser par le tableau de bord du jeu à chaque
|
tout ça vit désormais dans les onglets du tableau de bord (voir
|
||||||
fois. Remplace le fil d'Ariane (retiré, devenu redondant avec elle) :
|
game_dashboard.html) — donc un seul lien y mène. Remplace le fil
|
||||||
N'apparaît que si la route a mis un `game` dans le contexte du
|
d'Ariane (retiré, devenu redondant) : n'apparaît que si la route a mis
|
||||||
template (déjà fait par toutes les pages d'un jeu) — absente sur
|
un `game` dans le contexte du template (déjà fait par toutes les
|
||||||
l'accueil (liste des jeux), où il n'y a pas de jeu courant. "Jouer"
|
pages d'un jeu) — absente sur l'accueil (liste des jeux). "Jouer" est
|
||||||
est délibérément le DERNIER lien (les autres sont des éditeurs, celui-
|
délibérément le DERNIER lien — une action à part (ouvre l'aperçu
|
||||||
ci ouvre l'aperçu jouable dans un nouvel onglet — une action à part,
|
jouable dans un nouvel onglet), pas une page de gestion de plus. -->
|
||||||
pas un éditeur de plus). -->
|
|
||||||
<nav class="gameNavBar" aria-label="Navigation du jeu">
|
<nav class="gameNavBar" aria-label="Navigation du jeu">
|
||||||
<a href="{{ url_for('screens_list', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path.startswith('/game/' ~ game.slug ~ '/screens') else '' }}" title="Créer et enchaîner les écrans jouables">🖥️ Écrans du jeu</a>
|
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="Écrans, objets, éléments de jeu, variables">📊 Tableau de bord</a>
|
||||||
<a href="{{ url_for('element_types', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path.startswith('/game/' ~ game.slug ~ '/element-types') else '' }}" title="Catalogue réutilisable (Personnage, Outil, IA...)">🧩 Éléments de jeu</a>
|
|
||||||
<a href="{{ url_for('game_variables', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path.startswith('/game/' ~ game.slug ~ '/variables') else '' }}" title="Valeurs globales au jeu entier">🔧 Variables</a>
|
|
||||||
<a href="{{ url_for('object_new', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path.startswith('/game/' ~ game.slug ~ '/objects') else '' }}" title="Comme une table de base de données : un nom, et des champs typés">🗂️ Objet</a>
|
|
||||||
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu">▶️ Jouer</a>
|
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu">▶️ Jouer</a>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -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 %}
|
|
||||||
<h1 class="title">Éléments de jeu</h1>
|
|
||||||
<p class="hint mb-4">Un élément de jeu est un conteneur réutilisable.</p>
|
|
||||||
|
|
||||||
<div class="twoCol">
|
|
||||||
<div class="colLeft">
|
|
||||||
<h2>Créer</h2>
|
|
||||||
<form method="post" class="card">
|
|
||||||
<div class="fieldRow">
|
|
||||||
<div class="formField" style="flex:0 0 80px;">
|
|
||||||
<label for="icon">Icône</label>
|
|
||||||
<input type="text" id="icon" name="icon" value="🧩" maxlength="4">
|
|
||||||
</div>
|
|
||||||
<div class="formField" style="flex:1;">
|
|
||||||
<label for="name">Nom</label>
|
|
||||||
<input type="text" id="name" name="name" placeholder="ex. Personnage, Outil, Carte mail..." required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="formField">
|
|
||||||
<label for="bound_definition_id">Objet lié (optionnel)</label>
|
|
||||||
<select id="bound_definition_id" name="bound_definition_id">
|
|
||||||
<option value="">(aucun — élément décoratif, sans donnée)</option>
|
|
||||||
{% for d in definitions %}
|
|
||||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="actionsRow"><button type="submit" class="primary">+ Créer</button></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colRight">
|
|
||||||
<h2>Catalogue</h2>
|
|
||||||
{% if types %}
|
|
||||||
<div class="fieldsTableWrap">
|
|
||||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
|
||||||
<thead><tr><th>Nom</th><th>Usage</th><th></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for t in types %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.icon }} {{ t.name }}</td>
|
|
||||||
<td class="has-text-grey">
|
|
||||||
{{ 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 %}
|
|
||||||
</td>
|
|
||||||
<td class="fieldsTableActions">
|
|
||||||
<a class="button is-small" href="{{ url_for('screen_edit', slug=game.slug, screen_id=t.template_screen_id) }}" title="Modifier le contenu de cet élément de jeu">✏️</a>
|
|
||||||
<form method="post" action="{{ url_for('element_type_delete', slug=game.slug, element_type_id=t.id) }}"
|
|
||||||
onsubmit="return confirm('Supprimer l\'élément de jeu « {{ t.name }} » du catalogue (et tout son contenu) ?');">
|
|
||||||
<button type="submit" class="button is-small is-danger" {{ 'disabled title="Utilisé sur un écran — retire-le des écrans d\'abord"' if usage.get(t.id, 0) > 0 else '' }}>🗑️</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="hint">Aucun élément de jeu défini pour l'instant.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
{% block title %}{{ game.name }} — Forge Engine{% endblock %}
|
{% block title %}{{ game.name }} — Forge Engine{% endblock %}
|
||||||
{% block content_class %}content-wide{% endblock %}
|
{% block content_class %}content-wide{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title">{{ game.name }}</h1>
|
|
||||||
<p class="hint mb-3">Base de données : projects/{{ game.slug }}/game.db</p>
|
|
||||||
|
|
||||||
<!-- Une seule fenêtre, un onglet par catégorie (même principe que les
|
<!-- Une seule fenêtre, un onglet par catégorie (même principe que les
|
||||||
onglets "Écran / Logique / Timeline" de l'éditeur d'écran, voir
|
onglets "Écran / Logique / Timeline" de l'éditeur d'écran, voir
|
||||||
@@ -20,6 +18,11 @@
|
|||||||
|
|
||||||
<!-- ---------- Écrans ---------- -->
|
<!-- ---------- Écrans ---------- -->
|
||||||
<div class="builderTabPanel active" data-tab-panel="screens">
|
<div class="builderTabPanel active" data-tab-panel="screens">
|
||||||
|
{% if request.args.get('blocked_screen') %}
|
||||||
|
<div class="warningBanner">
|
||||||
|
⚠️ Suppression impossible : au moins un autre écran a un bouton qui cible précisément celui-ci. Retire d'abord cette action.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="dashPanelHeader">
|
<div class="dashPanelHeader">
|
||||||
<h2>Écrans du jeu</h2>
|
<h2>Écrans du jeu</h2>
|
||||||
<button type="button" class="button is-small primary" onclick="toggleDashCreate('screens')">+ Créer un écran</button>
|
<button type="button" class="button is-small primary" onclick="toggleDashCreate('screens')">+ Créer un écran</button>
|
||||||
@@ -63,9 +66,8 @@
|
|||||||
<div class="builderTabPanel" data-tab-panel="objects">
|
<div class="builderTabPanel" data-tab-panel="objects">
|
||||||
<div class="dashPanelHeader">
|
<div class="dashPanelHeader">
|
||||||
<h2>Objets</h2>
|
<h2>Objets</h2>
|
||||||
<a class="button is-small primary" href="{{ url_for('object_new', slug=game.slug) }}">+ Nouvel objet</a>
|
<button type="button" class="button is-small primary" onclick="openNewObjectPanel()">+ Nouvel objet</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint mb-2">Un objet a des champs typés (nombre, texte, relation...) — sa création se fait sur sa propre page.</p>
|
|
||||||
{% if definitions %}
|
{% if definitions %}
|
||||||
<div class="fieldsTableWrap">
|
<div class="fieldsTableWrap">
|
||||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||||
@@ -203,6 +205,104 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ---------- Panneau déplaçable : nouvel objet ---------- -->
|
||||||
|
<!-- Réutilise .floatPanel/.floatPanelHeader/.floatPanelBody (éditeur
|
||||||
|
d'écran, voir style.css) pour la même sensation de fenêtre flottante
|
||||||
|
qu'ailleurs dans l'outil, avec sa propre variante centrée
|
||||||
|
(.floatPanel--center) et son propre glisser-déposer minimal (pas de
|
||||||
|
position persistée en localStorage ici, contrairement aux panneaux de
|
||||||
|
l'éditeur d'écran — inutile pour un panneau ouvert ponctuellement).
|
||||||
|
Contenu repris de l'ancienne page "Définir un nouvel objet"
|
||||||
|
(object_form.html, retirée) : même formulaire, même gabarit de ligne
|
||||||
|
de champ, même script (object_form.js). -->
|
||||||
|
<div class="floatPanel floatPanel--center hidden" id="newObjectPanel">
|
||||||
|
<div class="floatPanelHeader" id="newObjectPanelHeader">
|
||||||
|
<span class="floatPanelTitle">⋮⋮ Nouvel objet</span>
|
||||||
|
<button type="button" class="button is-small" onclick="closeNewObjectPanel()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="floatPanelBody">
|
||||||
|
<form method="post" action="{{ url_for('object_new', slug=game.slug) }}" id="objectForm" class="formPassthrough">
|
||||||
|
<div class="formField">
|
||||||
|
<label for="object_name">Nom de l'objet (ex. Personnage, Niveau, Question...)</label>
|
||||||
|
<input type="text" id="object_name" name="object_name" required autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<h2 style="margin-top:16px;">Champs</h2>
|
||||||
|
<button type="button" class="button is-outlined is-fullwidth mb-2" id="btnAddField">+ Ajouter un champ</button>
|
||||||
|
<div id="fieldsList"></div>
|
||||||
|
<div class="actionsRow"><button type="submit" class="primary">Créer l'objet</button></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template id="fieldRowTemplate">
|
||||||
|
<div class="fieldRowCard box">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Nom du champ</label>
|
||||||
|
<div class="control"><input class="input" type="text" name="field_name[]" placeholder="ex. nom, description, pv..." required></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Type</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select name="field_type[]" class="fieldTypeSelect">
|
||||||
|
{% for key, val in field_types.items() %}
|
||||||
|
<option value="{{ key }}">{{ val.label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relationTarget field" style="display:none;">
|
||||||
|
<label class="label">Objet lié</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select name="field_relation[]">
|
||||||
|
{% for d in definitions %}
|
||||||
|
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if not definitions %}
|
||||||
|
<p class="help">Aucun autre objet défini.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="boundsTarget columns" style="display:none;">
|
||||||
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Valeur mini</label>
|
||||||
|
<div class="control"><input class="input" type="number" step="any" name="field_min[]" placeholder="ex. 0"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Valeur maxi</label>
|
||||||
|
<div class="control"><input class="input" type="number" step="any" name="field_max[]" placeholder="ex. 100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Obligatoire ?</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select is-fullwidth">
|
||||||
|
<select name="field_required[]">
|
||||||
|
<option value="0">Non</option>
|
||||||
|
<option value="1">Oui</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="button is-small is-danger btnRemoveField">🗑️ Retirer ce champ</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='object_form.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
function switchDashTab(tab) {
|
function switchDashTab(tab) {
|
||||||
document.querySelectorAll('#dashTabs .builderTabBtn').forEach(function (btn) {
|
document.querySelectorAll('#dashTabs .builderTabBtn').forEach(function (btn) {
|
||||||
@@ -220,5 +320,49 @@ function toggleDashCreate(tab) {
|
|||||||
if (firstInput) firstInput.focus();
|
if (firstInput) firstInput.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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).
|
||||||
|
(function () {
|
||||||
|
var requestedTab = {{ (request.args.get('tab') or '')|tojson }};
|
||||||
|
if (requestedTab) switchDashTab(requestedTab);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// ---------- Panneau déplaçable "Nouvel objet" ----------
|
||||||
|
(function () {
|
||||||
|
var panel = document.getElementById('newObjectPanel');
|
||||||
|
var header = document.getElementById('newObjectPanelHeader');
|
||||||
|
var dragging = false, offsetX = 0, offsetY = 0;
|
||||||
|
|
||||||
|
window.openNewObjectPanel = function () {
|
||||||
|
panel.classList.remove('hidden');
|
||||||
|
};
|
||||||
|
window.closeNewObjectPanel = function () {
|
||||||
|
panel.classList.add('hidden');
|
||||||
|
};
|
||||||
|
|
||||||
|
header.addEventListener('mousedown', function (e) {
|
||||||
|
if (e.target.closest('button')) return;
|
||||||
|
dragging = true;
|
||||||
|
var rect = panel.getBoundingClientRect();
|
||||||
|
panel.style.left = rect.left + 'px';
|
||||||
|
panel.style.top = rect.top + 'px';
|
||||||
|
panel.style.right = 'auto';
|
||||||
|
panel.style.transform = 'none';
|
||||||
|
offsetX = e.clientX - rect.left;
|
||||||
|
offsetY = e.clientY - rect.top;
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
});
|
||||||
|
document.addEventListener('mousemove', function (e) {
|
||||||
|
if (!dragging) return;
|
||||||
|
panel.style.left = (e.clientX - offsetX) + 'px';
|
||||||
|
panel.style.top = (e.clientY - offsetY) + 'px';
|
||||||
|
});
|
||||||
|
document.addEventListener('mouseup', function () {
|
||||||
|
dragging = false;
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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 %}
|
|
||||||
<h1 class="title">Variables globales</h1>
|
|
||||||
<p class="hint mb-4">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).</p>
|
|
||||||
|
|
||||||
<div class="twoCol">
|
|
||||||
<div class="colLeft">
|
|
||||||
<h2>Créer</h2>
|
|
||||||
<form method="post" action="{{ url_for('create_global_var', slug=game.slug) }}" class="card">
|
|
||||||
<div class="formField">
|
|
||||||
<label for="new-var-name">Nom</label>
|
|
||||||
<input type="text" id="new-var-name" name="name" placeholder="ex. porte_ouverte" required autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<div class="formField">
|
|
||||||
<label for="new-var-type">Type</label>
|
|
||||||
<select id="new-var-type" name="type">
|
|
||||||
{% for key, val in variable_types.items() %}
|
|
||||||
<option value="{{ key }}">{{ val.label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="formField">
|
|
||||||
<label for="new-var-value">Valeur de départ</label>
|
|
||||||
<input type="text" id="new-var-value" name="default_value" placeholder="ex. 0" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<div class="actionsRow"><button type="submit" class="primary">+ Créer la variable</button></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colRight">
|
|
||||||
<h2>Variables définies</h2>
|
|
||||||
{% if variables %}
|
|
||||||
{% for v in variables %}
|
|
||||||
<form method="post" action="{{ url_for('global_var_edit', slug=game.slug, var_id=v.id) }}" id="varEditForm{{ v.id }}"></form>
|
|
||||||
{% endfor %}
|
|
||||||
<div class="fieldsTableWrap">
|
|
||||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
|
||||||
<thead><tr><th>Nom</th><th>Type</th><th>Valeur</th><th></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for v in variables %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ v.name }}</td>
|
|
||||||
<td>
|
|
||||||
<select form="varEditForm{{ v.id }}" name="type" aria-label="Type de « {{ v.name }} »">
|
|
||||||
{% for key, val in variable_types.items() %}
|
|
||||||
<option value="{{ key }}" {{ 'selected' if v.type == key else '' }}>{{ val.label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td><input type="text" form="varEditForm{{ v.id }}" name="value" value="{{ v.value or '' }}" aria-label="Valeur de « {{ v.name }} »"></td>
|
|
||||||
<td class="fieldsTableActions">
|
|
||||||
<button type="submit" form="varEditForm{{ v.id }}" class="button is-small">Enregistrer</button>
|
|
||||||
<form method="post" action="{{ url_for('global_var_delete', slug=game.slug, var_id=v.id) }}"
|
|
||||||
onsubmit="return confirm('Supprimer la variable « {{ v.name }} » ? Toute condition de visibilité ou action qui la référence cessera de fonctionner.');">
|
|
||||||
<button type="submit" class="button is-small is-danger">🗑️</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="hint">Aucune variable pour l'instant — crée la première à gauche.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -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 %}
|
|
||||||
<h1 class="title">Définir un nouvel objet</h1>
|
|
||||||
|
|
||||||
<form method="post" id="objectForm" class="formPassthrough">
|
|
||||||
<div class="twoCol">
|
|
||||||
<div class="colLeft">
|
|
||||||
<h2>Objet</h2>
|
|
||||||
<div class="card">
|
|
||||||
<div class="formField">
|
|
||||||
<label for="object_name">Nom de l'objet (ex. Personnage, Niveau, Question...)</label>
|
|
||||||
<input type="text" id="object_name" name="object_name" required>
|
|
||||||
</div>
|
|
||||||
<div class="actionsRow"><button type="submit" class="primary">Créer l'objet</button></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colRight">
|
|
||||||
<h2>Champs</h2>
|
|
||||||
<button type="button" class="button is-outlined is-fullwidth mb-2" id="btnAddField">+ Ajouter un champ</button>
|
|
||||||
<div class="fieldsTableWrap" style="padding:10px;">
|
|
||||||
<div id="fieldsList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<template id="fieldRowTemplate">
|
|
||||||
<div class="fieldRowCard box">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Nom du champ</label>
|
|
||||||
<div class="control"><input class="input" type="text" name="field_name[]" placeholder="ex. nom, description, pv..." required></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Type</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select name="field_type[]" class="fieldTypeSelect">
|
|
||||||
{% for key, val in field_types.items() %}
|
|
||||||
<option value="{{ key }}">{{ val.label }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relationTarget field" style="display:none;">
|
|
||||||
<label class="label">Objet lié</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select name="field_relation[]">
|
|
||||||
{% for d in existing_definitions %}
|
|
||||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if not existing_definitions %}
|
|
||||||
<p class="help">Aucun autre objet défini.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="boundsTarget columns" style="display:none;">
|
|
||||||
<div class="column">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Valeur mini</label>
|
|
||||||
<div class="control"><input class="input" type="number" step="any" name="field_min[]" placeholder="ex. 0"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Valeur maxi</label>
|
|
||||||
<div class="control"><input class="input" type="number" step="any" name="field_max[]" placeholder="ex. 100"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Obligatoire ?</label>
|
|
||||||
<div class="control">
|
|
||||||
<div class="select is-fullwidth">
|
|
||||||
<select name="field_required[]">
|
|
||||||
<option value="0">Non</option>
|
|
||||||
<option value="1">Oui</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="button is-small is-danger btnRemoveField">🗑️ Retirer ce champ</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='object_form.js') }}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Pas encore d'élément de jeu personnalisé — <a href="{{ url_for('element_types', slug=game.slug) }}">crées-en un</a>.</p>
|
<p class="hint">Pas encore d'élément de jeu personnalisé — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='element_types') }}">crées-en un</a>.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
|
|
||||||
<div id="nodeFormCondition" style="display:none;">
|
<div id="nodeFormCondition" style="display:none;">
|
||||||
{% if not definitions %}
|
{% if not definitions %}
|
||||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<label>Objet<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
|
<label>Objet<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
|
||||||
{% for d in definitions %}
|
{% for d in definitions %}
|
||||||
@@ -313,7 +313,7 @@
|
|||||||
|
|
||||||
<div id="nf-action-data-fields" style="display:none;">
|
<div id="nf-action-data-fields" style="display:none;">
|
||||||
{% if not definitions %}
|
{% if not definitions %}
|
||||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='objects') }}">crées-en un</a>.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
|
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
|
||||||
{% for d in definitions %}
|
{% for d in definitions %}
|
||||||
@@ -712,7 +712,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{% if not global_variables %}
|
{% if not global_variables %}
|
||||||
<p class="help">Aucune variable globale pour l'instant — <a href="{{ url_for('game_variables', slug=game.slug) }}" target="_blank">crées-en une ↗</a></p>
|
<p class="help">Aucune variable globale pour l'instant — <a href="{{ url_for('game_dashboard', slug=game.slug, tab='variables') }}" target="_blank">crées-en une ↗</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -808,7 +808,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if c.key == 'visibilite_cond_variable' %}
|
{% if c.key == 'visibilite_cond_variable' %}
|
||||||
<p class="help">Pas la variable qu'il te faut ? <a href="{{ url_for('game_variables', slug=game.slug) }}" target="_blank">Gérer les variables du jeu ↗</a></p>
|
<p class="help">Pas la variable qu'il te faut ? <a href="{{ url_for('game_dashboard', slug=game.slug, tab='variables') }}" target="_blank">Gérer les variables du jeu ↗</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -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 %}
|
|
||||||
<h1 class="title">Écrans du jeu</h1>
|
|
||||||
{% if request.args.get('blocked_screen') %}
|
|
||||||
<div class="warningBanner">
|
|
||||||
⚠️ Suppression impossible : au moins un autre écran a un bouton qui cible précisément celui-ci. Retire d'abord cette action.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="twoCol">
|
|
||||||
<div class="colLeft">
|
|
||||||
<h2>Créer</h2>
|
|
||||||
<form method="post" action="{{ url_for('screens_new', slug=game.slug) }}" class="card">
|
|
||||||
<div class="formField">
|
|
||||||
<label for="name">Nom du nouvel écran</label>
|
|
||||||
<input type="text" id="name" name="name" placeholder="ex. Accueil, Formulaire d'inscription, Scène 1..." required>
|
|
||||||
</div>
|
|
||||||
<div class="actionsRow"><button type="submit" class="primary">+ Créer l'écran</button></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="colRight">
|
|
||||||
<h2>Enchaînement des écrans</h2>
|
|
||||||
{% if screen_list %}
|
|
||||||
<div class="fieldsTableWrap">
|
|
||||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
|
||||||
<thead><tr><th>#</th><th>Nom</th><th></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for s in screen_list %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ loop.index }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ url_for('screen_edit', slug=game.slug, screen_id=s.id) }}">{{ s.name }}</a>
|
|
||||||
{% if loop.first %} <span class="tag is-info is-light">★ Départ</span>{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="fieldsTableActions">
|
|
||||||
<form method="post" action="{{ url_for('screen_move', slug=game.slug, screen_id=s.id) }}"><input type="hidden" name="direction" value="up"><button type="submit" class="button is-small" {{ 'disabled' if loop.first else '' }}>↑</button></form>
|
|
||||||
<form method="post" action="{{ url_for('screen_move', slug=game.slug, screen_id=s.id) }}"><input type="hidden" name="direction" value="down"><button type="submit" class="button is-small" {{ 'disabled' if loop.last else '' }}>↓</button></form>
|
|
||||||
<a class="button is-small" href="{{ url_for('screen_edit', slug=game.slug, screen_id=s.id) }}">✏️</a>
|
|
||||||
<form method="post" action="{{ url_for('screen_delete', slug=game.slug, screen_id=s.id) }}"
|
|
||||||
onsubmit="return confirm('Supprimer l\'écran « {{ s.name }} » et tous ses éléments ?');">
|
|
||||||
<button type="submit" class="button is-small is-danger">🗑️</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="hint">Aucun écran pour l'instant — crée le premier à gauche.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -100,15 +100,17 @@ def test_element_type_template_case_a_and_case_b(client, game, mail_object):
|
|||||||
follow_redirects=False,
|
follow_redirects=False,
|
||||||
)
|
)
|
||||||
assert resp.status_code == 302
|
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)
|
m = re.search(r"/screens/(\d+)/edit", html)
|
||||||
assert m, "le lien vers l'écran-modèle doit apparaître dans le catalogue"
|
assert m, "le lien vers l'écran-modèle doit apparaître dans le catalogue"
|
||||||
template_screen_id = int(m.group(1))
|
template_screen_id = int(m.group(1))
|
||||||
|
|
||||||
# L'écran-modèle est un écran caché (is_template) : absent de la liste
|
# L'écran-modèle est un écran caché (is_template) : absent de la liste
|
||||||
# normale des écrans jouables.
|
# normale des écrans jouables (onglet "Écrans du jeu" du tableau de
|
||||||
screens_html = client.get(f"/game/{slug}/screens").data.decode()
|
# bord) — son nom complet ("Modèle : CarteMail") ne doit donc apparaître
|
||||||
assert "CarteMail" not in screens_html or "Modèle" not in screens_html
|
# 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".
|
# Construit le contenu du modèle : un titre lié au champ "sujet".
|
||||||
resp = client.post(
|
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):
|
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)
|
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)
|
m = re.search(r"/screens/(\d+)/edit", html)
|
||||||
template_screen_id = int(m.group(1))
|
template_screen_id = int(m.group(1))
|
||||||
element_type_id = int(re.search(r"element-types/(\d+)/delete", html).group(1))
|
element_type_id = int(re.search(r"element-types/(\d+)/delete", html).group(1))
|
||||||
|
|||||||
Reference in New Issue
Block a user