Reskin the whole admin UI (not just game widgets) with Bulma
Loads Bulma 1.0.2 in base.html (data-theme="dark" for its native dark palette) for every page that extends it, plus play.html directly. Converts every admin template — index, game dashboard, screens list, element types, object new/edit, data form/list — to real Bulma markup: navbar, breadcrumb component, box/card, field/control/input/select, table, notification, buttons, and a native Bulma modal for the data-row detail popup. Forge's own style.css keeps only what Bulma doesn't cover (the fixed-viewport builder/object-edit layouts, the element tree, canvas, flow-graph editor) and now acts as a secondary/override layer rather than a competing design system, matching how per-element inline customization already overrides widget defaults. screen_edit.html (the 3-panel screen builder) gets the same navbar/breadcrumb/button treatment plus its top rename form, but its flow-graph node editor, canvas and element tree keep their existing custom styling — several of their inputs have JS relying on exact DOM sibling structure (e.g. slider oninput reading nextElementSibling) or Bulma's own select/wrapper requirement, and reskinning them without a browser to verify against risked silently breaking the app's most complex feature. Buttons and headings there are still fully converted (safe, purely additive class changes). Verified: full test suite green, every route smoke-rendered 200 OK via the test client after the change.
This commit is contained in:
+15
-25
@@ -14,10 +14,12 @@ input[type=text], input[type=number], input[type=date], textarea, select{
|
||||
}
|
||||
input[type=checkbox]{ width:auto; margin-top:6px; }
|
||||
|
||||
.topbar{ display:flex; align-items:center; gap:10px; padding:12px 20px; background:var(--panel); border-bottom:1px solid var(--border); }
|
||||
.brand{ color:var(--text); font-weight:700; text-decoration:none; }
|
||||
.crumb{ color:var(--text-dim); text-decoration:none; font-size:13px; }
|
||||
.crumb-sep{ color:var(--text-dim); }
|
||||
/* .topbar/.breadcrumbBar : Bulma fournit déjà la mise en page (navbar,
|
||||
breadcrumb) — ces classes ne servent plus qu'à les repérer dans les
|
||||
layouts plein-écran (body.builderBody/objectEditBody, voir plus bas) et à
|
||||
les rattacher au thème sombre. */
|
||||
.topbar{ border-bottom:1px solid var(--border); }
|
||||
.breadcrumbBar{ padding:10px 20px; background:transparent; border-bottom:1px solid var(--border); margin-bottom:0; }
|
||||
.content{ max-width:760px; margin:0 auto; padding:28px 20px; }
|
||||
|
||||
.card{ border:1px solid var(--border); border-radius:10px; padding:16px; background:var(--panel); margin:12px 0; }
|
||||
@@ -59,28 +61,16 @@ button.danger{ color:var(--danger); border-color:rgba(226,104,95,.4); }
|
||||
.fieldRowCard{ margin-bottom:10px; }
|
||||
.relationTarget{ margin-top:6px; }
|
||||
|
||||
.dataTableWrap{ overflow-x:auto; max-width:100%; }
|
||||
.dataTable{ width:100%; border-collapse:collapse; font-size:12.5px; }
|
||||
.dataTable th, .dataTable td{ border:1px solid var(--border); padding:6px 8px; text-align:left; }
|
||||
.dataTable th{ background:var(--panel2); color:var(--text-dim); white-space:nowrap; }
|
||||
/* .dataTable : Bulma fournit déjà la mise en page du tableau (classes
|
||||
"table is-fullwidth is-hoverable", voir data_list.html) — ceci ne fait
|
||||
plus que tronquer les cellules trop longues avec "...", pour garder une
|
||||
vue d'ensemble compacte (le détail complet s'ouvre dans la modale Bulma
|
||||
ci-dessous via le bouton 👁️). */
|
||||
.dataTable td{ max-width:220px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
|
||||
/* ---------- Détail d'une entrée (bouton 👁️) ---------- */
|
||||
/* Popup plein écran assombri, pour lire une ligne dont les colonnes sont
|
||||
tronquées dans le tableau (voir .dataTable td ci-dessus) sans avoir à
|
||||
ouvrir le formulaire de modification. */
|
||||
.rowDetailModal{
|
||||
display:none; position:fixed; inset:0; background:rgba(0,0,0,.6);
|
||||
align-items:center; justify-content:center; z-index:100; padding:20px;
|
||||
}
|
||||
.rowDetailModal.open{ display:flex; }
|
||||
.rowDetailCard{
|
||||
background:var(--panel); border:1px solid var(--border); border-radius:10px;
|
||||
max-width:640px; width:100%; max-height:80vh; display:flex; flex-direction:column;
|
||||
}
|
||||
.rowDetailHeader{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px; border-bottom:1px solid var(--border); flex:0 0 auto; }
|
||||
.rowDetailHeader h3{ margin:0; color:var(--text); font-size:15px; }
|
||||
.rowDetailBody{ padding:6px 16px 16px; overflow-y:auto; }
|
||||
/* Contenu de la modale Bulma (.modal/.modal-card, voir data_list.html) —
|
||||
affiche chaque champ en entier, sans troncature. */
|
||||
.rowDetailField{ padding:10px 0; border-bottom:1px solid var(--border); }
|
||||
.rowDetailField:last-child{ border-bottom:none; }
|
||||
.rowDetailLabel{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; margin-bottom:4px; }
|
||||
@@ -110,7 +100,7 @@ button.danger{ color:var(--danger); border-color:rgba(226,104,95,.4); }
|
||||
a beaucoup de champs, pour garder une vue d'ensemble sans perdre le
|
||||
formulaire "Ajouter un champ" hors de l'écran. */
|
||||
body.objectEditBody{ display:flex; flex-direction:column; height:100vh; margin:0; overflow:hidden; }
|
||||
body.objectEditBody > .topbar{ flex:0 0 auto; }
|
||||
body.objectEditBody > .topbar, body.objectEditBody > .breadcrumbBar{ flex:0 0 auto; }
|
||||
body.objectEditBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; display:flex; flex-direction:column; }
|
||||
|
||||
.content-objectEdit{ width:100%; display:flex; flex-direction:column; min-height:0; }
|
||||
@@ -136,7 +126,7 @@ body.objectEditBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden
|
||||
ce sont les zones internes (panneaux, écran, panneau de logique) qui
|
||||
défilent chacune de leur côté si besoin. */
|
||||
body.builderBody{ display:flex; flex-direction:column; height:100vh; margin:0; overflow:hidden; }
|
||||
body.builderBody > .topbar{ flex:0 0 auto; }
|
||||
body.builderBody > .topbar, body.builderBody > .breadcrumbBar{ flex:0 0 auto; }
|
||||
body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; display:flex; flex-direction:column; }
|
||||
|
||||
.content-builder{
|
||||
|
||||
+15
-5
@@ -1,17 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<html lang="fr" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Forge Engine{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="{% block body_class %}{% endblock %}">
|
||||
<header class="topbar">
|
||||
<a href="{{ url_for('index') }}" class="brand">🛠️ Forge Engine</a>
|
||||
{% block breadcrumb %}{% endblock %}
|
||||
</header>
|
||||
<nav class="navbar topbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a href="{{ url_for('index') }}" class="navbar-item brand"><strong>🛠️ Forge Engine</strong></a>
|
||||
</div>
|
||||
</nav>
|
||||
{% block breadcrumb_wrap %}
|
||||
<nav class="breadcrumb breadcrumbBar" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{{ url_for('index') }}">🛠️ Mes jeux</a></li>
|
||||
{% block breadcrumb %}{% endblock %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
<main class="content {% block content_class %}{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
+27
-24
@@ -1,57 +1,60 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ 'Modifier' if row else 'Ajouter' }} — {{ definition.name }}{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span>
|
||||
<a class="crumb" href="{{ url_for('object_view', slug=game.slug, definition_id=definition.id) }}">{{ definition.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">{{ 'Modifier #' ~ row.id if row else 'Nouvelle entrée' }}</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li><a href="{{ url_for('object_view', slug=game.slug, definition_id=definition.id) }}">{{ definition.name }}</a></li>
|
||||
<li class="is-active"><a>{{ 'Modifier #' ~ row.id if row else 'Nouvelle entrée' }}</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ 'Modifier l\'entrée #' ~ row.id if row else 'Ajouter une entrée' }} — {{ definition.name }}</h1>
|
||||
<p class="hint">Formulaire généré automatiquement à partir de la définition de cet objet — aucun champ n'est codé en dur ici.</p>
|
||||
<h1 class="title">{{ 'Modifier l\'entrée #' ~ row.id if row else 'Ajouter une entrée' }} — {{ definition.name }}</h1>
|
||||
<p class="help mb-4">Formulaire généré automatiquement à partir de la définition de cet objet — aucun champ n'est codé en dur ici.</p>
|
||||
|
||||
<form method="post" class="card">
|
||||
<form method="post" class="box">
|
||||
{% for f in definition.fields %}
|
||||
{% set col = f.name|colname %}
|
||||
{% set current = row[col] if row else none %}
|
||||
<div class="formField">
|
||||
<label for="field_{{ f.id }}">{{ f.name }}{% if f.required %} *{% endif %}</label>
|
||||
<div class="field">
|
||||
<label class="label" for="field_{{ f.id }}">{{ f.name }}{% if f.required %} *{% endif %}</label>
|
||||
<div class="control">
|
||||
|
||||
{% if f.type == 'texte' %}
|
||||
<input type="text" id="field_{{ f.id }}" name="{{ col }}" value="{{ current or '' }}" {{ 'required' if f.required else '' }}>
|
||||
<input class="input" type="text" id="field_{{ f.id }}" name="{{ col }}" value="{{ current or '' }}" {{ 'required' if f.required else '' }}>
|
||||
|
||||
{% elif f.type == 'texte_long' %}
|
||||
<textarea id="field_{{ f.id }}" name="{{ col }}" rows="4" {{ 'required' if f.required else '' }}>{{ current or '' }}</textarea>
|
||||
<textarea class="textarea" id="field_{{ f.id }}" name="{{ col }}" rows="4" {{ 'required' if f.required else '' }}>{{ current or '' }}</textarea>
|
||||
|
||||
{% elif f.type == 'nombre_entier' %}
|
||||
<input type="number" step="1" id="field_{{ f.id }}" name="{{ col }}" value="{{ current if current is not none else '' }}" {{ 'required' if f.required else '' }}>
|
||||
<input class="input" type="number" step="1" id="field_{{ f.id }}" name="{{ col }}" value="{{ current if current is not none else '' }}" {{ 'required' if f.required else '' }}>
|
||||
|
||||
{% elif f.type == 'nombre_decimal' %}
|
||||
<input type="number" step="any" id="field_{{ f.id }}" name="{{ col }}" value="{{ current if current is not none else '' }}" {{ 'required' if f.required else '' }}>
|
||||
<input class="input" type="number" step="any" id="field_{{ f.id }}" name="{{ col }}" value="{{ current if current is not none else '' }}" {{ 'required' if f.required else '' }}>
|
||||
|
||||
{% elif f.type == 'booleen' %}
|
||||
<input type="checkbox" id="field_{{ f.id }}" name="{{ col }}" {{ 'checked' if current else '' }}>
|
||||
<label class="checkbox"><input type="checkbox" id="field_{{ f.id }}" name="{{ col }}" {{ 'checked' if current else '' }}> Oui</label>
|
||||
|
||||
{% elif f.type == 'date' %}
|
||||
<input type="date" id="field_{{ f.id }}" name="{{ col }}" value="{{ current or '' }}" {{ 'required' if f.required else '' }}>
|
||||
<input class="input" type="date" id="field_{{ f.id }}" name="{{ col }}" value="{{ current or '' }}" {{ 'required' if f.required else '' }}>
|
||||
|
||||
{% elif f.type == 'relation' %}
|
||||
{% set current_rel = row[col ~ '_id'] if row else none %}
|
||||
<select id="field_{{ f.id }}" name="{{ col }}_id" {{ 'required' if f.required else '' }}>
|
||||
<option value="">—</option>
|
||||
{% for opt in relation_options.get(f.id, []) %}
|
||||
<option value="{{ opt.id }}" {{ 'selected' if current_rel == opt.id else '' }}>{{ opt.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="select is-fullwidth">
|
||||
<select id="field_{{ f.id }}" name="{{ col }}_id" {{ 'required' if f.required else '' }}>
|
||||
<option value="">—</option>
|
||||
{% for opt in relation_options.get(f.id, []) %}
|
||||
<option value="{{ opt.id }}" {{ 'selected' if current_rel == opt.id else '' }}>{{ opt.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% if not relation_options.get(f.id) %}
|
||||
<p class="hint">Aucune entrée n'existe encore pour l'objet lié — crée-en une d'abord si tu veux pouvoir la sélectionner ici.</p>
|
||||
<p class="help">Aucune entrée n'existe encore pour l'objet lié — crée-en une d'abord si tu veux pouvoir la sélectionner ici.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="submit" class="primary">{{ 'Enregistrer les modifications' if row else 'Enregistrer en base' }}</button>
|
||||
<div class="field">
|
||||
<button type="submit" class="button is-primary">{{ 'Enregistrer les modifications' if row else 'Enregistrer en base' }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
+28
-27
@@ -1,17 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ definition.name }} — {{ game.name }}{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">{{ definition.name }}</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li class="is-active"><a>{{ definition.name }}</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ definition.name }}
|
||||
<a class="smallBtn" href="{{ url_for('object_edit', slug=game.slug, definition_id=definition.id) }}" title="Modifier cet objet" style="text-decoration:none; margin-left:6px;">✏️</a>
|
||||
<h1 class="title">{{ definition.name }}
|
||||
<a class="button is-small" href="{{ url_for('object_edit', slug=game.slug, definition_id=definition.id) }}" title="Modifier cet objet">✏️</a>
|
||||
</h1>
|
||||
<p class="hint">Table SQL : {{ definition.table_name }} — {{ definition.fields|length }} champ(s)</p>
|
||||
<p class="help mb-4">Table SQL : {{ definition.table_name }} — {{ definition.fields|length }} champ(s)</p>
|
||||
|
||||
{% if referenced_by %}
|
||||
<div class="warningBanner">
|
||||
<div class="notification is-warning is-light">
|
||||
⚠️ D'autres objets ont une relation vers « {{ definition.name }} » :
|
||||
{{ referenced_by|map(attribute='name')|join(', ') }}.
|
||||
La suppression de cet objet est bloquée tant que ces relations existent.
|
||||
@@ -19,16 +19,16 @@
|
||||
{% endif %}
|
||||
|
||||
{% if blocked_row_id %}
|
||||
<div class="warningBanner">
|
||||
<div class="notification is-warning is-light">
|
||||
⚠️ Impossible de supprimer l'entrée #{{ blocked_row_id }} : elle est référencée par
|
||||
{% for ref in blocked_row_refs %}{{ ref.count }} entrée(s) de « {{ ref.definition_name }} » (champ « {{ ref.field_name }} »){% if not loop.last %}, {% endif %}{% endfor %}.
|
||||
Modifie ou supprime d'abord ces entrées.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a class="addBtn" href="{{ url_for('data_new', slug=game.slug, definition_id=definition.id) }}">+ Ajouter une entrée</a>
|
||||
<a class="button is-primary is-outlined mb-4" href="{{ url_for('data_new', slug=game.slug, definition_id=definition.id) }}">+ Ajouter une entrée</a>
|
||||
|
||||
<h2>Données enregistrées</h2>
|
||||
<h2 class="subtitle">Données enregistrées</h2>
|
||||
{% if rows %}
|
||||
{% macro row_field_display(f, r) %}
|
||||
{%- if f.type == 'relation' -%}
|
||||
@@ -39,8 +39,8 @@
|
||||
{{ r[f.name|colname] }}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
<div class="dataTableWrap">
|
||||
<table class="dataTable">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
@@ -56,12 +56,12 @@
|
||||
<td>{{ row_field_display(f, r) }}</td>
|
||||
{% endfor %}
|
||||
<td>
|
||||
<div class="rowActions">
|
||||
<button type="button" class="smallBtn" title="Voir cette entrée en détail" onclick="openRowDetail({{ r.id }})">👁️</button>
|
||||
<a class="smallBtn" href="{{ url_for('data_edit', slug=game.slug, definition_id=definition.id, row_id=r.id) }}">✏️</a>
|
||||
<div class="buttons is-inline-flex are-small">
|
||||
<button type="button" class="button is-small" title="Voir cette entrée en détail" onclick="openRowDetail({{ r.id }})">👁️</button>
|
||||
<a class="button is-small" href="{{ url_for('data_edit', slug=game.slug, definition_id=definition.id, row_id=r.id) }}">✏️</a>
|
||||
<form method="post" action="{{ url_for('data_delete', slug=game.slug, definition_id=definition.id, row_id=r.id) }}"
|
||||
onsubmit="return confirm('Supprimer définitivement l\'entrée #{{ r.id }} ?');">
|
||||
<button type="submit" class="smallBtn danger">🗑️</button>
|
||||
<button type="submit" class="button is-small is-danger">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
@@ -72,41 +72,42 @@
|
||||
</div>
|
||||
|
||||
{% for r in rows %}
|
||||
<div class="rowDetailModal" id="rowDetail{{ r.id }}" onclick="if(event.target===this) closeRowDetail({{ r.id }});">
|
||||
<div class="rowDetailCard">
|
||||
<div class="rowDetailHeader">
|
||||
<h3>Entrée #{{ r.id }}</h3>
|
||||
<button type="button" class="smallBtn" title="Fermer" onclick="closeRowDetail({{ r.id }})">✕</button>
|
||||
</div>
|
||||
<div class="rowDetailBody">
|
||||
<div class="modal" id="rowDetail{{ r.id }}">
|
||||
<div class="modal-background" onclick="closeRowDetail({{ r.id }})"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Entrée #{{ r.id }}</p>
|
||||
<button class="delete" aria-label="Fermer" onclick="closeRowDetail({{ r.id }})"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
{% for f in definition.fields %}
|
||||
<div class="rowDetailField">
|
||||
<div class="rowDetailLabel">{{ f.name }}</div>
|
||||
<div class="rowDetailValue">{{ row_field_display(f, r) }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="hint">Aucune donnée enregistrée pour l'instant.</p>
|
||||
<p class="help">Aucune donnée enregistrée pour l'instant.</p>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function openRowDetail(id) {
|
||||
var modal = document.getElementById('rowDetail' + id);
|
||||
if (modal) modal.classList.add('open');
|
||||
if (modal) modal.classList.add('is-active');
|
||||
}
|
||||
function closeRowDetail(id) {
|
||||
var modal = document.getElementById('rowDetail' + id);
|
||||
if (modal) modal.classList.remove('open');
|
||||
if (modal) modal.classList.remove('is-active');
|
||||
}
|
||||
if (!window.__forgeRowDetailEscBound) {
|
||||
window.__forgeRowDetailEscBound = true;
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
document.querySelectorAll('.rowDetailModal.open').forEach(function (m) { m.classList.remove('open'); });
|
||||
document.querySelectorAll('.modal.is-active').forEach(function (m) { m.classList.remove('is-active'); });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,63 +1,76 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Éléments de jeu — {{ game.name }}{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">Éléments de jeu</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li class="is-active"><a>Éléments de jeu</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Éléments de jeu</h1>
|
||||
<p class="hint">Un élément de jeu est un conteneur réutilisable.</p>
|
||||
<h1 class="title">Éléments de jeu</h1>
|
||||
<p class="help mb-4">Un élément de jeu est un conteneur réutilisable.</p>
|
||||
|
||||
<form method="post" class="card">
|
||||
<div class="fieldRow">
|
||||
<div style="flex:0 0 80px;">
|
||||
<label for="icon">Icône</label>
|
||||
<input type="text" id="icon" name="icon" value="🧩" maxlength="4">
|
||||
<form method="post" class="box">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow" style="width:100px;">
|
||||
<div class="field">
|
||||
<label class="label" for="icon">Icône</label>
|
||||
<div class="control"><input class="input" type="text" id="icon" name="icon" value="🧩" maxlength="4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<label for="name">Nom</label>
|
||||
<input type="text" id="name" name="name" placeholder="ex. Personnage, Outil, Carte mail..." required>
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label" for="name">Nom</label>
|
||||
<div class="control"><input class="input" type="text" id="name" name="name" placeholder="ex. Personnage, Outil, Carte mail..." required></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fieldRow">
|
||||
<div>
|
||||
<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 class="field">
|
||||
<label class="label" for="bound_definition_id">Objet lié (optionnel)</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<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>
|
||||
</div>
|
||||
<div class="actionsRow"><button type="submit" class="primary">+ Créer</button></div>
|
||||
<div class="field"><button type="submit" class="button is-primary">+ Créer</button></div>
|
||||
</form>
|
||||
|
||||
<h2>Catalogue</h2>
|
||||
<h2 class="subtitle">Catalogue</h2>
|
||||
{% if types %}
|
||||
<div class="list">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
{% for t in types %}
|
||||
<div class="listRow listRowFlex">
|
||||
<div class="rowLink">
|
||||
<div class="rowTitle">{{ t.icon }} {{ t.name }}</div>
|
||||
<div class="rowSub">
|
||||
<tr>
|
||||
<td class="is-vcentered">
|
||||
<span class="has-text-weight-semibold">{{ t.icon }} {{ t.name }}</span>
|
||||
<br>
|
||||
<span class="is-size-7 has-text-grey">
|
||||
{{ usage.get(t.id, 0) }} instance(s) posée(s) sur des écrans
|
||||
{% if t.bound_definition_id %}
|
||||
{% for d in definitions %}{% if d.id == t.bound_definition_id %} · lié à l'objet « {{ d.name }} »{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="is-vcentered has-text-right">
|
||||
<div class="buttons is-inline-flex are-small">
|
||||
<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">✏️ Modifier le contenu</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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rowActions">
|
||||
<a class="smallBtn" href="{{ url_for('screen_edit', slug=game.slug, screen_id=t.template_screen_id) }}" title="Modifier le contenu de cet élément de jeu">✏️ Modifier le contenu</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="smallBtn danger" {{ 'disabled title="Utilisé sur un écran — retire-le des écrans d\'abord"' if usage.get(t.id, 0) > 0 else '' }}>🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun élément de jeu défini pour l'instant.</p>
|
||||
<p class="help">Aucun élément de jeu défini pour l'instant.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,61 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ game.name }} — Forge Engine{% endblock %}
|
||||
{% block breadcrumb %}<span class="crumb">{{ game.name }}</span>{% endblock %}
|
||||
{% block breadcrumb %}<li class="is-active"><a>{{ game.name }}</a></li>{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ game.name }}</h1>
|
||||
<p class="hint">Base de données : projects/{{ game.slug }}/game.db</p>
|
||||
<h1 class="title">{{ game.name }}</h1>
|
||||
<p class="help mb-4">Base de données : projects/{{ game.slug }}/game.db</p>
|
||||
|
||||
<div class="fieldRow" style="margin:14px 0;">
|
||||
<a class="listRow rowLink" style="flex:1;" href="{{ url_for('screens_list', slug=game.slug) }}">
|
||||
<div class="rowTitle">🖥️ Écrans du jeu</div>
|
||||
<div class="rowSub">Créer et enchaîner les écrans jouables (accueil, formulaires, scènes...)</div>
|
||||
</a>
|
||||
<a class="listRow rowLink" style="flex:1;" href="{{ url_for('element_types', slug=game.slug) }}">
|
||||
<div class="rowTitle">🧩 Éléments de jeu</div>
|
||||
<div class="rowSub">Catalogue réutilisable (Personnage, Outil, IA...)</div>
|
||||
</a>
|
||||
<a class="listRow rowLink" style="flex:1;" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">
|
||||
<div class="rowTitle">▶️ Jouer</div>
|
||||
<div class="rowSub">Aperçu jouable du jeu</div>
|
||||
</a>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<a href="{{ url_for('screens_list', slug=game.slug) }}" class="card" style="height:100%;">
|
||||
<div class="card-content">
|
||||
<p class="title is-6">🖥️ Écrans du jeu</p>
|
||||
<p class="subtitle is-7 has-text-grey">Créer et enchaîner les écrans jouables (accueil, formulaires, scènes...)</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="{{ url_for('element_types', slug=game.slug) }}" class="card" style="height:100%;">
|
||||
<div class="card-content">
|
||||
<p class="title is-6">🧩 Éléments de jeu</p>
|
||||
<p class="subtitle is-7 has-text-grey">Catalogue réutilisable (Personnage, Outil, IA...)</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="card" style="height:100%;">
|
||||
<div class="card-content">
|
||||
<p class="title is-6">▶️ Jouer</p>
|
||||
<p class="subtitle is-7 has-text-grey">Aperçu jouable du jeu</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="listRow rowLink" href="{{ url_for('object_new', slug=game.slug) }}">
|
||||
<div class="rowTitle">➕ Définir un nouvel objet</div>
|
||||
<div class="rowSub">Comme une table de base de données : un nom, et des champs typés</div>
|
||||
<a href="{{ url_for('object_new', slug=game.slug) }}" class="card mb-4" style="display:block;">
|
||||
<div class="card-content">
|
||||
<p class="title is-6">➕ Définir un nouvel objet</p>
|
||||
<p class="subtitle is-7 has-text-grey">Comme une table de base de données : un nom, et des champs typés</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<h2>Objets définis</h2>
|
||||
<h2 class="subtitle">Objets définis</h2>
|
||||
{% if definitions %}
|
||||
<div class="list">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
{% for d in definitions %}
|
||||
<div class="listRow listRowFlex">
|
||||
<a class="rowLink" href="{{ url_for('object_view', slug=game.slug, definition_id=d.id) }}">
|
||||
<div class="rowTitle">{{ d.name }}</div>
|
||||
<div class="rowSub">table SQL : {{ d.table_name }}</div>
|
||||
</a>
|
||||
<div class="rowActions">
|
||||
<a class="smallBtn" href="{{ url_for('object_edit', slug=game.slug, definition_id=d.id) }}" title="Modifier cet objet">✏️</a>
|
||||
</div>
|
||||
</div>
|
||||
<tr>
|
||||
<td class="is-vcentered">
|
||||
<a href="{{ url_for('object_view', slug=game.slug, definition_id=d.id) }}" class="has-text-weight-semibold">{{ d.name }}</a>
|
||||
<br><span class="is-size-7 has-text-grey">table SQL : {{ d.table_name }}</span>
|
||||
</td>
|
||||
<td class="is-vcentered has-text-right">
|
||||
<a class="button is-small" href="{{ url_for('object_edit', slug=game.slug, definition_id=d.id) }}" title="Modifier cet objet">✏️</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">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.</p>
|
||||
<p class="help">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.</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="dangerZone">
|
||||
<h3>Paramètres du jeu</h3>
|
||||
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}" class="fieldRow" style="align-items:flex-end;">
|
||||
<div>
|
||||
<label for="rename">Renommer le jeu</label>
|
||||
<input type="text" id="rename" name="game_name" value="{{ game.name }}" required autocomplete="off">
|
||||
<div class="box has-background-danger-dark mt-5">
|
||||
<h3 class="title is-6 has-text-danger-light">⚠️ Paramètres du jeu</h3>
|
||||
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}" class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<label class="label has-text-danger-light" for="rename">Renommer le jeu</label>
|
||||
<input class="input" type="text" id="rename" name="game_name" value="{{ game.name }}" required autocomplete="off">
|
||||
</div>
|
||||
<div class="control" style="align-self:flex-end;">
|
||||
<button type="submit" class="button">Renommer</button>
|
||||
</div>
|
||||
<button type="submit">Renommer</button>
|
||||
</form>
|
||||
<form method="post" action="{{ url_for('game_delete', slug=game.slug) }}" style="margin-top:12px;"
|
||||
<form method="post" action="{{ url_for('game_delete', slug=game.slug) }}" class="mt-4"
|
||||
onsubmit="return confirm('Supprimer définitivement le jeu « {{ game.name }} », son dossier et sa base de données ? Cette action est irréversible.');">
|
||||
<button type="submit" class="danger">🗑️ Supprimer ce jeu</button>
|
||||
<button type="submit" class="button is-danger">🗑️ Supprimer ce jeu</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
+33
-20
@@ -1,34 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Forge Engine — Mes jeux{% endblock %}
|
||||
{% block breadcrumb_wrap %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Mes jeux</h1>
|
||||
<h1 class="title">Mes jeux</h1>
|
||||
|
||||
<form method="post" action="{{ url_for('games_new') }}" class="card inline-form">
|
||||
<label for="name">Nom du nouveau jeu</label>
|
||||
<div class="fieldRow">
|
||||
<input type="text" id="name" name="name" placeholder="ex. CyberQuest RGPD" required>
|
||||
<button type="submit" class="primary">+ Créer le jeu</button>
|
||||
<form method="post" action="{{ url_for('games_new') }}" class="box">
|
||||
<div class="field">
|
||||
<label class="label" for="name">Nom du nouveau jeu</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" id="name" name="name" placeholder="ex. CyberQuest RGPD" required>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">+ Créer le jeu</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help">Crée un dossier avec index.html / index.css / index.js reliés entre eux, et une base de données dédiée à ce jeu.</p>
|
||||
</div>
|
||||
<p class="hint">Crée un dossier avec index.html / index.css / index.js reliés entre eux, et une base de données dédiée à ce jeu.</p>
|
||||
</form>
|
||||
|
||||
<h2>Jeux existants</h2>
|
||||
<h2 class="subtitle">Jeux existants</h2>
|
||||
{% if games %}
|
||||
<div class="list">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
{% for g in games %}
|
||||
<div class="listRow listRowFlex">
|
||||
<a class="rowLink" href="{{ url_for('game_dashboard', slug=g.slug) }}">
|
||||
<div class="rowTitle">{{ g.name }}</div>
|
||||
<div class="rowSub">projects/{{ g.slug }}/</div>
|
||||
</a>
|
||||
<form method="post" action="{{ url_for('game_delete', slug=g.slug) }}"
|
||||
onsubmit="return confirm('Supprimer définitivement le jeu « {{ g.name }} » et tout son contenu (dossier + base de données) ?');">
|
||||
<button type="submit" class="smallBtn danger" title="Supprimer ce jeu">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
<tr>
|
||||
<td class="is-vcentered">
|
||||
<a href="{{ url_for('game_dashboard', slug=g.slug) }}" class="has-text-weight-semibold">{{ g.name }}</a>
|
||||
<br><span class="is-size-7 has-text-grey">projects/{{ g.slug }}/</span>
|
||||
</td>
|
||||
<td class="is-vcentered has-text-right">
|
||||
<form method="post" action="{{ url_for('game_delete', slug=g.slug) }}" class="is-inline-block"
|
||||
onsubmit="return confirm('Supprimer définitivement le jeu « {{ g.name }} » et tout son contenu (dossier + base de données) ?');">
|
||||
<button type="submit" class="button is-small is-danger" title="Supprimer ce jeu">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun jeu pour l'instant — crée le premier ci-dessus.</p>
|
||||
<p class="help">Aucun jeu pour l'instant — crée le premier ci-dessus.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
+93
-68
@@ -3,84 +3,105 @@
|
||||
{% block content_class %}content-wide content-objectEdit{% endblock %}
|
||||
{% block body_class %}objectEditBody{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span>
|
||||
<a class="crumb" href="{{ url_for('object_view', slug=game.slug, definition_id=definition.id) }}">{{ definition.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">Modifier</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li><a href="{{ url_for('object_view', slug=game.slug, definition_id=definition.id) }}">{{ definition.name }}</a></li>
|
||||
<li class="is-active"><a>Modifier</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Modifier l'objet « {{ definition.name }} »</h1>
|
||||
<p class="hint">Table SQL : {{ definition.table_name }}</p>
|
||||
<h1 class="title">Modifier l'objet « {{ definition.name }} »</h1>
|
||||
<p class="help mb-4">Table SQL : {{ definition.table_name }}</p>
|
||||
|
||||
<form method="post" class="card fieldRow" style="align-items:flex-end;">
|
||||
<div>
|
||||
<label for="object_name">Nom de l'objet</label>
|
||||
<input type="text" id="object_name" name="object_name" value="{{ definition.name }}" required>
|
||||
<form method="post" class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<label class="label" for="object_name">Nom de l'objet</label>
|
||||
<input class="input" type="text" id="object_name" name="object_name" value="{{ definition.name }}" required>
|
||||
</div>
|
||||
<div class="control" style="align-self:flex-end;">
|
||||
<button type="submit" class="button">Renommer</button>
|
||||
</div>
|
||||
<button type="submit">Renommer</button>
|
||||
</form>
|
||||
|
||||
<div class="twoCol">
|
||||
<!-- Colonne de gauche : ajouter un champ + supprimer l'objet -->
|
||||
<div class="colLeft">
|
||||
<h2>Ajouter un champ</h2>
|
||||
<form method="post" action="{{ url_for('object_field_add', slug=game.slug, definition_id=definition.id) }}" class="card" id="addFieldForm">
|
||||
<label>Nom du champ</label>
|
||||
<input type="text" name="field_name" placeholder="ex. nom, description, pv..." required>
|
||||
<h2 class="subtitle">Ajouter un champ</h2>
|
||||
<form method="post" action="{{ url_for('object_field_add', slug=game.slug, definition_id=definition.id) }}" class="box" id="addFieldForm">
|
||||
<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>
|
||||
|
||||
<label>Type</label>
|
||||
<select name="field_type" id="newFieldType">
|
||||
{% for key, val in field_types.items() %}
|
||||
<option value="{{ key }}">{{ val.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="field">
|
||||
<label class="label">Type</label>
|
||||
<div class="control">
|
||||
<div class="select is-fullwidth">
|
||||
<select name="field_type" id="newFieldType">
|
||||
{% for key, val in field_types.items() %}
|
||||
<option value="{{ key }}">{{ val.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="newFieldRelationWrap" style="display:none; margin-top:8px;">
|
||||
<label>Objet lié</label>
|
||||
<select name="field_relation">
|
||||
{% for d in existing_definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="newFieldRelationWrap" class="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="hint">Aucun autre objet défini dans ce jeu pour l'instant.</p>
|
||||
<p class="help">Aucun autre objet défini.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="newFieldBoundsWrap" class="fieldRow" style="display:none; margin-top:8px;">
|
||||
<div>
|
||||
<label>Valeur mini (optionnel)</label>
|
||||
<input type="number" step="any" name="field_min" placeholder="ex. 0">
|
||||
<div id="newFieldBoundsWrap" class="columns" style="display:none;">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label">Valeur mini (optionnel)</label>
|
||||
<div class="control"><input class="input" type="number" step="any" name="field_min" placeholder="ex. 0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Valeur maxi (optionnel)</label>
|
||||
<input type="number" step="any" name="field_max" placeholder="ex. 100">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label">Valeur maxi (optionnel)</label>
|
||||
<div class="control"><input class="input" type="number" step="any" name="field_max" placeholder="ex. 100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Obligatoire ?</label>
|
||||
<select name="field_required">
|
||||
<option value="0">Non</option>
|
||||
<option value="1">Oui</option>
|
||||
</select>
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="submit" class="primary">+ Ajouter ce champ</button>
|
||||
<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>
|
||||
|
||||
<div class="field"><button type="submit" class="button is-primary">+ Ajouter ce champ</button></div>
|
||||
</form>
|
||||
|
||||
<div class="dangerZone">
|
||||
<h3>Supprimer cet objet</h3>
|
||||
<div class="box has-background-danger-dark mt-4">
|
||||
<h3 class="title is-6 has-text-danger-light">Supprimer cet objet</h3>
|
||||
<form method="post" action="{{ url_for('object_delete', slug=game.slug, definition_id=definition.id) }}"
|
||||
onsubmit="return confirm('Supprimer définitivement l\'objet « {{ definition.name }} », sa table et toutes ses données ? Cette action est irréversible.');">
|
||||
<button type="submit" class="danger">🗑️ Supprimer l'objet « {{ definition.name }} »</button>
|
||||
<button type="submit" class="button is-danger">🗑️ Supprimer l'objet « {{ definition.name }} »</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne de droite : champs existants, chacun modifiable -->
|
||||
<div class="colRight">
|
||||
<h2>Champs existants</h2>
|
||||
<h2 class="subtitle">Champs existants</h2>
|
||||
{% if definition.fields %}
|
||||
{% for f in definition.fields %}
|
||||
<form method="post" action="{{ url_for('object_field_edit', slug=game.slug, definition_id=definition.id, field_id=f.id) }}" id="fieldEditForm{{ f.id }}"></form>
|
||||
@@ -88,7 +109,7 @@
|
||||
onsubmit="return confirm('Retirer le champ « {{ f.name }} » ? Les données déjà enregistrées dans ce champ seront perdues.');"></form>
|
||||
{% endfor %}
|
||||
<div class="fieldsTableWrap">
|
||||
<table class="fieldsTable">
|
||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom du champ</th>
|
||||
@@ -103,51 +124,55 @@
|
||||
<tbody>
|
||||
{% for f in definition.fields %}
|
||||
<tr>
|
||||
<td><input type="text" form="fieldEditForm{{ f.id }}" name="field_name" value="{{ f.name }}" aria-label="Nom du champ « {{ f.name }} »" required></td>
|
||||
<td><input class="input is-small" type="text" form="fieldEditForm{{ f.id }}" name="field_name" value="{{ f.name }}" aria-label="Nom du champ « {{ f.name }} »" required></td>
|
||||
<td>{{ field_types.get(f.type, {}).get('label', f.type) }}</td>
|
||||
<td>
|
||||
{% if f.type == 'relation' %}
|
||||
<select form="fieldEditForm{{ f.id }}" name="field_relation" aria-label="Objet lié pour « {{ f.name }} »">
|
||||
{% for d in existing_definitions %}
|
||||
<option value="{{ d.id }}" {{ 'selected' if d.id == f.relation_definition_id else '' }}>{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="select is-small">
|
||||
<select form="fieldEditForm{{ f.id }}" name="field_relation" aria-label="Objet lié pour « {{ f.name }} »">
|
||||
{% for d in existing_definitions %}
|
||||
<option value="{{ d.id }}" {{ 'selected' if d.id == f.relation_definition_id else '' }}>{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="hint">—</span>
|
||||
<span class="has-text-grey">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if f.type in ('nombre_entier', 'nombre_decimal') %}
|
||||
<input type="number" step="any" form="fieldEditForm{{ f.id }}" name="field_min" value="{{ f.min_value if f.min_value is not none else '' }}" placeholder="ex. 0" aria-label="Valeur mini pour « {{ f.name }} »">
|
||||
<input class="input is-small" type="number" step="any" form="fieldEditForm{{ f.id }}" name="field_min" value="{{ f.min_value if f.min_value is not none else '' }}" placeholder="ex. 0" aria-label="Valeur mini pour « {{ f.name }} »">
|
||||
{% else %}
|
||||
<span class="hint">—</span>
|
||||
<span class="has-text-grey">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if f.type in ('nombre_entier', 'nombre_decimal') %}
|
||||
<input type="number" step="any" form="fieldEditForm{{ f.id }}" name="field_max" value="{{ f.max_value if f.max_value is not none else '' }}" placeholder="ex. 100" aria-label="Valeur maxi pour « {{ f.name }} »">
|
||||
<input class="input is-small" type="number" step="any" form="fieldEditForm{{ f.id }}" name="field_max" value="{{ f.max_value if f.max_value is not none else '' }}" placeholder="ex. 100" aria-label="Valeur maxi pour « {{ f.name }} »">
|
||||
{% else %}
|
||||
<span class="hint">—</span>
|
||||
<span class="has-text-grey">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<select form="fieldEditForm{{ f.id }}" name="field_required" aria-label="« {{ f.name }} » obligatoire ?">
|
||||
<option value="0" {{ 'selected' if not f.required else '' }}>Non</option>
|
||||
<option value="1" {{ 'selected' if f.required else '' }}>Oui</option>
|
||||
</select>
|
||||
<div class="select is-small">
|
||||
<select form="fieldEditForm{{ f.id }}" name="field_required" aria-label="« {{ f.name }} » obligatoire ?">
|
||||
<option value="0" {{ 'selected' if not f.required else '' }}>Non</option>
|
||||
<option value="1" {{ 'selected' if f.required else '' }}>Oui</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="fieldsTableActions">
|
||||
<button type="submit" form="fieldEditForm{{ f.id }}" class="smallBtn">Enregistrer</button>
|
||||
<button type="submit" form="fieldDeleteForm{{ f.id }}" class="smallBtn danger">🗑️</button>
|
||||
<button type="submit" form="fieldEditForm{{ f.id }}" class="button is-small">Enregistrer</button>
|
||||
<button type="submit" form="fieldDeleteForm{{ f.id }}" class="button is-small is-danger">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="hint">Mini/Maxi (bornage automatique) : laisser vide = pas de limite. S'applique à "Donner cette valeur", "Augmenter de..." et "Diminuer de..." dans le graphe de logique.</p>
|
||||
<p class="help">Mini/Maxi (bornage automatique) : laisser vide = pas de limite. S'applique à "Donner cette valeur", "Augmenter de..." et "Diminuer de..." dans le graphe de logique.</p>
|
||||
{% else %}
|
||||
<p class="hint">Aucun champ pour l'instant.</p>
|
||||
<p class="help">Aucun champ pour l'instant.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+66
-44
@@ -1,70 +1,92 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Nouvel objet — {{ game.name }}{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">Nouvel objet</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li class="is-active"><a>Nouvel objet</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Définir un nouvel objet</h1>
|
||||
<h1 class="title">Définir un nouvel objet</h1>
|
||||
|
||||
<form method="post" class="card" id="objectForm">
|
||||
<label for="object_name">Nom de l'objet (ex. Personnage, Niveau, Question...)</label>
|
||||
<input type="text" id="object_name" name="object_name" required>
|
||||
<form method="post" class="box" id="objectForm">
|
||||
<div class="field">
|
||||
<label class="label" for="object_name">Nom de l'objet (ex. Personnage, Niveau, Question...)</label>
|
||||
<div class="control"><input class="input" type="text" id="object_name" name="object_name" required></div>
|
||||
</div>
|
||||
|
||||
<h3>Champs</h3>
|
||||
<h3 class="title is-6">Champs</h3>
|
||||
<div id="fieldsList"></div>
|
||||
<button type="button" class="addBtn" id="btnAddField">+ Ajouter un champ</button>
|
||||
<button type="button" class="button is-outlined is-fullwidth mb-4" id="btnAddField">+ Ajouter un champ</button>
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="submit" class="primary">Créer l'objet</button>
|
||||
<div class="field">
|
||||
<button type="submit" class="button is-primary">Créer l'objet</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template id="fieldRowTemplate">
|
||||
<div class="fieldRowCard card">
|
||||
<div class="fieldRow">
|
||||
<div>
|
||||
<label>Nom du champ</label>
|
||||
<input type="text" name="field_name[]" placeholder="ex. nom, description, pv..." required>
|
||||
<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>
|
||||
<label>Type</label>
|
||||
<select name="field_type[]" class="fieldTypeSelect">
|
||||
{% for key, val in field_types.items() %}
|
||||
<option value="{{ key }}">{{ val.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<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" style="display:none;">
|
||||
<label>Objet lié</label>
|
||||
<select name="field_relation[]">
|
||||
{% for d in existing_definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<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="hint">Aucun autre objet défini.</p>
|
||||
<p class="help">Aucun autre objet défini.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="boundsTarget fieldRow" style="display:none;">
|
||||
<div>
|
||||
<label>Valeur mini</label>
|
||||
<input type="number" step="any" name="field_min[]" placeholder="ex. 0">
|
||||
<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>
|
||||
<label>Valeur maxi</label>
|
||||
<input type="number" step="any" name="field_max[]" placeholder="ex. 100">
|
||||
<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>
|
||||
<label>Obligatoire ?</label>
|
||||
<select name="field_required[]">
|
||||
<option value="0">Non</option>
|
||||
<option value="1">Oui</option>
|
||||
</select>
|
||||
<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="smallBtn danger btnRemoveField">🗑️ Retirer ce champ</button>
|
||||
<button type="button" class="button is-small is-danger btnRemoveField">🗑️ Retirer ce champ</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+27
-35
@@ -5,28 +5,20 @@
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="{{ google_fonts_link }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<!-- Bulma : moteur de style par défaut de certains widgets (Bouton, Titre,
|
||||
Conteneur en carte) — voir screens/widgets/control_groups/bulma_controls.py.
|
||||
Chargé APRÈS style.css : les styles en ligne posés par le panneau de
|
||||
propriétés (couleur, taille personnalisées...) gagnent de toute façon
|
||||
toujours contre une classe CSS, donc l'ordre ne change rien au résultat
|
||||
— Bulma ne fournit qu'un habillage par défaut, jamais un dernier mot. -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
||||
{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span>
|
||||
<a class="crumb" href="{{ url_for('screens_list', slug=game.slug) }}">Écrans</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">{{ screen.name }}</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li><a href="{{ url_for('screens_list', slug=game.slug) }}">Écrans</a></li>
|
||||
<li class="is-active"><a>{{ screen.name }}</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<form method="post" action="{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class="fieldRow" style="align-items:center; margin:0;">
|
||||
<input type="text" name="name" value="{{ screen.name }}" required style="font-size:16px; font-weight:700; width:auto;">
|
||||
<button type="submit" class="smallBtn">Renommer</button>
|
||||
<form method="post" action="{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class="field has-addons" style="align-items:center; margin:0;">
|
||||
<div class="control"><input class="input" type="text" name="name" value="{{ screen.name }}" required style="font-size:16px; font-weight:700; width:auto;"></div>
|
||||
<div class="control"><button type="submit" class="button is-small">Renommer</button></div>
|
||||
</form>
|
||||
{% if not screen.is_template %}
|
||||
<a class="smallBtn primary" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
|
||||
<a class="button is-small is-primary" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -67,7 +59,7 @@
|
||||
{% set el = elements|selectattr("id", "equalto", selected_id)|first if selected_id else none %}
|
||||
<!-- Panneau gauche : ajouter un élément (icônes, sans jargon technique) -->
|
||||
<div class="builderPanel">
|
||||
<h2>Éléments de cet écran</h2>
|
||||
<h2 class="subtitle is-6">Éléments de cet écran</h2>
|
||||
{% if top_level_elements %}
|
||||
<div class="elementTree">
|
||||
{% for el in top_level_elements %}
|
||||
@@ -162,12 +154,12 @@
|
||||
<h2 style="margin:0;">{{ w.get('icon','❔') }} {{ el|elabel }}{% if el.name %} <span class="hint">— {{ w.get('label', el.widget) }}</span>{% endif %}</h2>
|
||||
<form method="post" action="{{ url_for('element_delete', slug=game.slug, element_id=el.id) }}"
|
||||
onsubmit="return confirm('Supprimer cet élément ?');">
|
||||
<button type="submit" class="smallBtn danger">🗑️</button>
|
||||
<button type="submit" class="smallBtn button is-small is-danger">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('element_rename', slug=game.slug, element_id=el.id) }}" class="fieldRow" style="align-items:center; margin:6px 0;">
|
||||
<input type="text" name="name" value="{{ el.name or '' }}" placeholder="Donne un nom à cet élément" style="flex:1;">
|
||||
<button type="submit" class="smallBtn">Nommer</button>
|
||||
<button type="submit" class="smallBtn button is-small">Nommer</button>
|
||||
</form>
|
||||
<div class="fieldRow" style="align-items:center; margin:6px 0;">
|
||||
<label style="width:auto; margin-right:6px;">ID</label>
|
||||
@@ -204,12 +196,12 @@
|
||||
<div class="listRow" style="display:flex; flex-direction:column; gap:6px;">
|
||||
<form method="post" action="{{ url_for('element_rename', slug=game.slug, element_id=tab.id) }}" class="fieldRow" style="align-items:center; margin:0;">
|
||||
<input type="text" name="name" value="{{ tab.name or ('Onglet ' ~ loop.index) }}" placeholder="Nom de l'onglet" style="flex:1;">
|
||||
<button type="submit" class="smallBtn">Renommer</button>
|
||||
<button type="submit" class="smallBtn button is-small">Renommer</button>
|
||||
</form>
|
||||
<div class="fieldRow" style="align-items:center; margin:0;">
|
||||
<a class="smallBtn" style="flex:1; text-align:center;" href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=tab.id) }}">📥 Contenu de l'onglet</a>
|
||||
<a class="smallBtn button is-small" style="flex:1; text-align:center;" href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=tab.id) }}">📥 Contenu de l'onglet</a>
|
||||
<form method="post" action="{{ url_for('element_delete', slug=game.slug, element_id=tab.id) }}" onsubmit="return confirm('Supprimer cet onglet et tout son contenu ?');">
|
||||
<button type="submit" class="smallBtn danger">🗑️</button>
|
||||
<button type="submit" class="smallBtn button is-small is-danger">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +211,7 @@
|
||||
<p class="hint">Aucun onglet pour l'instant.</p>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('element_tab_add', slug=game.slug, element_id=el.id) }}" style="margin-top:8px;">
|
||||
<button type="submit" class="smallBtn primary" style="width:100%;">➕ Ajouter un onglet</button>
|
||||
<button type="submit" class="smallBtn button is-small is-primary" style="width:100%;">➕ Ajouter un onglet</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
@@ -356,16 +348,16 @@
|
||||
</div>
|
||||
<div class="flowPanelBody" id="flowPanelBody">
|
||||
<div class="flowToolbar">
|
||||
<button type="button" class="smallBtn" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
|
||||
<button type="button" class="smallBtn" onclick="openNodeForm('condition')">➕ Condition</button>
|
||||
<button type="button" class="smallBtn" onclick="openNodeForm('action')">➕ Action</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('condition')">➕ Condition</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('action')">➕ Action</button>
|
||||
<span class="hint">Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.</span>
|
||||
</div>
|
||||
|
||||
<div id="nodeForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="nodeFormTitle">Nouveau nœud</strong>
|
||||
<button type="button" class="smallBtn" onclick="closeNodeForm()">✕</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeNodeForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<div id="nodeFormTrigger" style="display:none;">
|
||||
@@ -409,7 +401,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<div id="nf-cond-extra-clauses"></div>
|
||||
<button type="button" class="smallBtn" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -497,7 +489,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionsRow"><button type="button" class="smallBtn primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
|
||||
<div class="actionsRow"><button type="button" class="smallBtn button is-small is-primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
|
||||
</div>
|
||||
|
||||
<div class="flowCanvasWrap">
|
||||
@@ -532,13 +524,13 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="smallBtn" onclick="addAnimTrack()">➕ Ajouter un clip</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimTrack()">➕ Ajouter un clip</button>
|
||||
</div>
|
||||
|
||||
<div id="animClipForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="animClipFormTitle">Clip d'animation</strong>
|
||||
<button type="button" class="smallBtn" onclick="closeAnimClipForm()">✕</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeAnimClipForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<label>Type
|
||||
@@ -564,7 +556,7 @@
|
||||
|
||||
<div id="anim-clip-custom-fields" style="display:none;">
|
||||
<div id="anim-clip-keyframes"></div>
|
||||
<button type="button" class="smallBtn" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
|
||||
</div>
|
||||
|
||||
<div class="fieldRow">
|
||||
@@ -599,9 +591,9 @@
|
||||
</label>
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="button" class="smallBtn primary" onclick="saveAnimClipForm()">Enregistrer</button>
|
||||
<button type="button" class="smallBtn" onclick="previewAnimClip()">▶ Aperçu</button>
|
||||
<button type="button" class="smallBtn danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
|
||||
<button type="button" class="smallBtn button is-small is-primary" onclick="saveAnimClipForm()">Enregistrer</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="previewAnimClip()">▶ Aperçu</button>
|
||||
<button type="button" class="smallBtn button is-small is-danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1546,7 +1538,7 @@ function renderFlowConditionClauses() {
|
||||
CONDITION_OPERATORS_HTML +
|
||||
'</select></label>' +
|
||||
'<label>Valeur de comparaison<input type="text" id="nf-cond-extra-value-' + idx + '" placeholder="ex. 1, vrai, 10..." value="' + (clause.value || '').replace(/"/g, '"') + '" onchange="flowConditionExtraClauses[' + idx + '].value = this.value"></label>' +
|
||||
'<button type="button" class="smallBtn" onclick="removeFlowConditionClause(' + idx + ')">Retirer cette condition</button>' +
|
||||
'<button type="button" class="smallBtn button is-small" onclick="removeFlowConditionClause(' + idx + ')">Retirer cette condition</button>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
flowConditionExtraClauses.forEach(function(clause, idx) {
|
||||
|
||||
+39
-26
@@ -1,47 +1,60 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Écrans — {{ game.name }}{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
<a class="crumb" href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a>
|
||||
<span class="crumb-sep">/</span><span class="crumb">Écrans</span>
|
||||
<li><a href="{{ url_for('game_dashboard', slug=game.slug) }}">{{ game.name }}</a></li>
|
||||
<li class="is-active"><a>Écrans</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Écrans du jeu</h1>
|
||||
<h1 class="title">Écrans du jeu</h1>
|
||||
|
||||
<form method="post" action="{{ url_for('screens_new', slug=game.slug) }}" class="card inline-form">
|
||||
<label for="name">Nom du nouvel écran</label>
|
||||
<div class="fieldRow">
|
||||
<input type="text" id="name" name="name" placeholder="ex. Accueil, Formulaire d'inscription, Scène 1..." required>
|
||||
<button type="submit" class="primary">+ Créer l'écran</button>
|
||||
<form method="post" action="{{ url_for('screens_new', slug=game.slug) }}" class="box">
|
||||
<div class="field">
|
||||
<label class="label" for="name">Nom du nouvel écran</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" id="name" name="name" placeholder="ex. Accueil, Formulaire d'inscription, Scène 1..." required>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">+ Créer l'écran</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if request.args.get('blocked_screen') %}
|
||||
<div class="warningBanner">
|
||||
<div class="notification is-warning is-light">
|
||||
⚠️ Suppression impossible : au moins un autre écran a un bouton qui cible précisément celui-ci. Retire d'abord cette action.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>Enchaînement des écrans</h2>
|
||||
<h2 class="subtitle">Enchaînement des écrans</h2>
|
||||
{% if screen_list %}
|
||||
<div class="list">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
{% for s in screen_list %}
|
||||
<div class="listRow listRowFlex">
|
||||
<a class="rowLink" href="{{ url_for('screen_edit', slug=game.slug, screen_id=s.id) }}">
|
||||
<div class="rowTitle">{{ loop.index }}. {{ s.name }}{% if loop.first %} <span class="hint">★ Écran de départ</span>{% endif %}</div>
|
||||
</a>
|
||||
<div class="rowActions">
|
||||
<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="smallBtn" {{ '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="smallBtn" {{ 'disabled' if loop.last else '' }}>↓</button></form>
|
||||
<a class="smallBtn" 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="smallBtn danger">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<tr>
|
||||
<td class="is-vcentered">
|
||||
<a href="{{ url_for('screen_edit', slug=game.slug, screen_id=s.id) }}">{{ loop.index }}. {{ s.name }}</a>
|
||||
{% if loop.first %} <span class="tag is-info is-light">★ Écran de départ</span>{% endif %}
|
||||
</td>
|
||||
<td class="is-vcentered has-text-right">
|
||||
<div class="buttons is-inline-flex are-small">
|
||||
<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>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun écran pour l'instant — crée le premier ci-dessus.</p>
|
||||
<p class="help">Aucun écran pour l'instant — crée le premier ci-dessus.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user