Refait le dashboard en fenêtre à onglets avec de vrais tableaux
Le précédent dashboard (grille de cartes compactes) ne correspondait pas
à ce que l'utilisateur voulait : une seule fenêtre avec de vrais tableaux
de données (denses, colonnes nettes), un bouton "Créer" par catégorie
dans l'en-tête, et une navigation horizontale pour passer d'une catégorie
à l'autre.
Réutilise telles quelles .builderTabs/.builderTabBtn/.builderTabPanel
(déjà utilisées pour "Écran / Logique / Timeline" dans l'éditeur d'écran)
plutôt que d'inventer un 2e système d'onglets — même sensation partout
dans l'outil. Un onglet par catégorie (Écrans/Objets/Éléments de
jeu/Variables), chacun avec :
- un bouton "+ Créer" dans l'en-tête qui révèle une barre de création
compacte (repliée par défaut) — sauf pour les Objets, dont la
création (plusieurs champs typés) reste sur sa propre page dédiée,
trop complexe pour tenir dans une barre ;
- le VRAI tableau de gestion de cette catégorie (colonnes, actions),
repris tel quel de screens_list.html/element_types.html/
game_variables.html plutôt que réinventé en version appauvrie.
La page défile désormais normalement (retrait de body.objectEditBody/
content-objectEdit, pensés pour une hauteur figée avec défilement
interne) — une liste peut être longue, pas besoin d'un défilement séparé
par panneau ici.
routes/games/game_dashboard.py fournit en plus variable_types
(db.GLOBAL_VARIABLE_TYPES) pour la barre de création de variable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8f45169209
commit
65450a5719
@@ -24,4 +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,
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-13
@@ -494,16 +494,16 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p
|
|||||||
.animKeyframeRow textarea{ font-family:monospace; font-size:11px; }
|
.animKeyframeRow textarea{ font-family:monospace; font-size:11px; }
|
||||||
|
|
||||||
/* ---------- Tableau de bord d'un jeu (game_dashboard.html) ---------- */
|
/* ---------- Tableau de bord d'un jeu (game_dashboard.html) ---------- */
|
||||||
/* Résumé/point d'entrée : une carte par catégorie (écrans, objets,
|
/* Une seule fenêtre, un onglet par catégorie (écrans/objets/éléments de
|
||||||
éléments de jeu, variables), toutes de même hauteur dans leur ligne
|
jeu/variables) — réutilise .builderTabs/.builderTabBtn/.builderTabPanel
|
||||||
(align-items:stretch, par défaut en grid) pour un rendu net même quand
|
(voir plus bas, éditeur d'écran) pour la même sensation de "fenêtre à
|
||||||
une carte a beaucoup plus d'entrées qu'une autre. */
|
onglets" qu'ailleurs dans l'outil, plutôt que d'inventer un 2e système.
|
||||||
.dashboardGrid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(260px, 1fr)); gap:16px; margin-top:8px; }
|
Contrairement à leur usage dans l'éditeur d'écran, ici la page défile
|
||||||
.dashboardCard{ display:flex; flex-direction:column; border:1px solid var(--border); border-radius:10px; padding:16px; background:var(--panel); }
|
normalement (pas de hauteur figée) : une liste peut être longue, pas
|
||||||
.dashboardCard h2{ display:flex; align-items:center; justify-content:space-between; margin:0 0 10px; }
|
besoin d'un défilement interne séparé par panneau. */
|
||||||
.dashboardCardCount{ font-size:11px; color:var(--text-dim); background:var(--panel2); border-radius:999px; padding:2px 8px; text-transform:none; letter-spacing:0; }
|
.dashTabCount{ font-size:10px; color:var(--text-dim); background:var(--panel2); border-radius:999px; padding:1px 6px; margin-left:4px; }
|
||||||
.dashboardCardList{ display:flex; flex-direction:column; gap:2px; flex:1 1 auto; min-height:0; }
|
.dashPanelHeader{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; }
|
||||||
.dashboardCardItem{ display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 8px; border-radius:6px; text-decoration:none; color:var(--text); font-size:12.5px; }
|
.dashPanelHeader h2{ margin:0; }
|
||||||
a.dashboardCardItem:hover{ background:var(--panel2); }
|
.dashCreateBar{ display:flex; gap:8px; align-items:center; margin-bottom:12px; padding:10px; border:1px solid var(--border); border-radius:8px; background:var(--panel); }
|
||||||
.dashboardCardItem span:first-child{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.dashCreateBar input, .dashCreateBar select{ width:auto; flex:1 1 160px; }
|
||||||
.dashboardCardFooter{ margin-top:10px; padding-top:10px; border-top:1px solid var(--border); font-size:12.5px; }
|
.dashDangerZone{ margin-top:28px; }
|
||||||
|
|||||||
+174
-50
@@ -3,87 +3,192 @@
|
|||||||
{% block content_class %}content-wide{% endblock %}
|
{% block content_class %}content-wide{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title">{{ game.name }}</h1>
|
<h1 class="title">{{ game.name }}</h1>
|
||||||
<p class="hint mb-4">Base de données : projects/{{ game.slug }}/game.db</p>
|
<p class="hint mb-3">Base de données : projects/{{ game.slug }}/game.db</p>
|
||||||
|
|
||||||
<div class="dashboardGrid">
|
<!-- Une seule fenêtre, un onglet par catégorie (même principe que les
|
||||||
<div class="dashboardCard">
|
onglets "Écran / Logique / Timeline" de l'éditeur d'écran, voir
|
||||||
<h2>🖥️ Écrans du jeu <span class="dashboardCardCount">{{ screen_list|length }}</span></h2>
|
.builderTabs/.builderTabBtn/.builderTabPanel dans style.css) — pour
|
||||||
|
naviguer entre écrans/objets/éléments de jeu/variables sans jamais
|
||||||
|
changer de page, avec à chaque fois un vrai tableau (dense, colonnes
|
||||||
|
nettes) plutôt que des cartes compactes. -->
|
||||||
|
<div class="builderTabs" id="dashTabs">
|
||||||
|
<button type="button" class="builderTabBtn active" data-tab="screens" onclick="switchDashTab('screens')">🖥️ Écrans du jeu <span class="dashTabCount">{{ screen_list|length }}</span></button>
|
||||||
|
<button type="button" class="builderTabBtn" data-tab="objects" onclick="switchDashTab('objects')">🗂️ Objets <span class="dashTabCount">{{ definitions|length }}</span></button>
|
||||||
|
<button type="button" class="builderTabBtn" data-tab="element_types" onclick="switchDashTab('element_types')">🧩 Éléments de jeu <span class="dashTabCount">{{ element_types|length }}</span></button>
|
||||||
|
<button type="button" class="builderTabBtn" data-tab="variables" onclick="switchDashTab('variables')">🔧 Variables <span class="dashTabCount">{{ variables|length }}</span></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ---------- Écrans ---------- -->
|
||||||
|
<div class="builderTabPanel active" data-tab-panel="screens">
|
||||||
|
<div class="dashPanelHeader">
|
||||||
|
<h2>Écrans du jeu</h2>
|
||||||
|
<button type="button" class="button is-small primary" onclick="toggleDashCreate('screens')">+ Créer un écran</button>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('screens_new', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-screens" style="display:none;">
|
||||||
|
<input type="text" name="name" placeholder="Nom du nouvel écran (ex. Accueil, Scène 1...)" required autocomplete="off">
|
||||||
|
<button type="submit" class="primary">Créer</button>
|
||||||
|
</form>
|
||||||
{% if screen_list %}
|
{% if screen_list %}
|
||||||
<div class="dashboardCardList">
|
<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 %}
|
{% for s in screen_list %}
|
||||||
<a class="dashboardCardItem" href="{{ url_for('screen_edit', slug=game.slug, screen_id=s.id) }}">
|
<tr>
|
||||||
<span>{{ s.name }}</span>
|
<td>{{ loop.index }}</td>
|
||||||
{% if loop.first %}<span class="tag is-info is-light">★ Départ</span>{% endif %}
|
<td>
|
||||||
</a>
|
<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 %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Aucun écran pour l'instant.</p>
|
<p class="hint">Aucun écran pour l'instant.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dashboardCardFooter">
|
</div>
|
||||||
<a href="{{ url_for('screens_list', slug=game.slug) }}">Gérer les écrans →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboardCard">
|
<!-- ---------- Objets ---------- -->
|
||||||
<h2>🗂️ Objets <span class="dashboardCardCount">{{ definitions|length }}</span></h2>
|
<div class="builderTabPanel" data-tab-panel="objects">
|
||||||
|
<div class="dashPanelHeader">
|
||||||
|
<h2>Objets</h2>
|
||||||
|
<a class="button is-small primary" href="{{ url_for('object_new', slug=game.slug) }}">+ Nouvel objet</a>
|
||||||
|
</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="dashboardCardList">
|
<div class="fieldsTableWrap">
|
||||||
|
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||||
|
<thead><tr><th>Nom</th><th>Table SQL</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
{% for d in definitions %}
|
{% for d in definitions %}
|
||||||
<a class="dashboardCardItem" href="{{ url_for('object_view', slug=game.slug, definition_id=d.id) }}">
|
<tr>
|
||||||
<span>{{ d.name }}</span>
|
<td><a href="{{ url_for('object_view', slug=game.slug, definition_id=d.id) }}">{{ d.name }}</a></td>
|
||||||
<span class="has-text-grey">{{ d.table_name }}</span>
|
<td class="has-text-grey">{{ d.table_name }}</td>
|
||||||
</a>
|
<td class="fieldsTableActions">
|
||||||
|
<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 %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Aucun objet pour l'instant.</p>
|
<p class="hint">Aucun objet pour l'instant.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dashboardCardFooter">
|
</div>
|
||||||
<a href="{{ url_for('object_new', slug=game.slug) }}">+ Nouvel objet →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboardCard">
|
<!-- ---------- Éléments de jeu ---------- -->
|
||||||
<h2>🧩 Éléments de jeu <span class="dashboardCardCount">{{ element_types|length }}</span></h2>
|
<div class="builderTabPanel" data-tab-panel="element_types">
|
||||||
{% if element_types %}
|
<div class="dashPanelHeader">
|
||||||
<div class="dashboardCardList">
|
<h2>Éléments de jeu</h2>
|
||||||
{% for t in element_types %}
|
<button type="button" class="button is-small primary" onclick="toggleDashCreate('element_types')">+ Créer un élément de jeu</button>
|
||||||
<a class="dashboardCardItem" href="{{ url_for('screen_edit', slug=game.slug, screen_id=t.template_screen_id) }}">
|
</div>
|
||||||
<span>{{ t.icon or '🧩' }} {{ t.name }}</span>
|
<form method="post" action="{{ url_for('element_types', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-element_types" style="display:none;">
|
||||||
<span class="has-text-grey">{{ element_type_usage.get(t.id, 0) }} instance(s)</span>
|
<input type="text" name="icon" value="🧩" maxlength="4" style="max-width:60px;" title="Icône">
|
||||||
</a>
|
<input type="text" name="name" placeholder="Nom (ex. Personnage, Carte mail...)" required autocomplete="off">
|
||||||
|
<select name="bound_definition_id">
|
||||||
|
<option value="">(aucun objet lié)</option>
|
||||||
|
{% for d in definitions %}
|
||||||
|
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="primary">Créer</button>
|
||||||
|
</form>
|
||||||
|
{% if element_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 element_types %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ t.icon or '🧩' }} {{ t.name }}</td>
|
||||||
|
<td class="has-text-grey">
|
||||||
|
{{ element_type_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 element_type_usage.get(t.id, 0) > 0 else '' }}>🗑️</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Aucun élément de jeu pour l'instant.</p>
|
<p class="hint">Aucun élément de jeu pour l'instant.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dashboardCardFooter">
|
</div>
|
||||||
<a href="{{ url_for('element_types', slug=game.slug) }}">Gérer les éléments de jeu →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboardCard">
|
<!-- ---------- Variables ---------- -->
|
||||||
<h2>🔧 Variables <span class="dashboardCardCount">{{ variables|length }}</span></h2>
|
<div class="builderTabPanel" data-tab-panel="variables">
|
||||||
{% if variables %}
|
<div class="dashPanelHeader">
|
||||||
<div class="dashboardCardList">
|
<h2>Variables</h2>
|
||||||
{% for v in variables %}
|
<button type="button" class="button is-small primary" onclick="toggleDashCreate('variables')">+ Créer une variable</button>
|
||||||
<div class="dashboardCardItem">
|
|
||||||
<span>{{ v.name }}</span>
|
|
||||||
<span class="has-text-grey">{{ v.value or '' }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('create_global_var', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-variables" style="display:none;">
|
||||||
|
<input type="text" name="name" placeholder="Nom (ex. porte_ouverte)" required autocomplete="off">
|
||||||
|
<select name="type">
|
||||||
|
{% for key, val in variable_types.items() %}
|
||||||
|
<option value="{{ key }}">{{ val.label }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="text" name="default_value" placeholder="Valeur de départ">
|
||||||
|
<button type="submit" class="primary">Créer</button>
|
||||||
|
</form>
|
||||||
|
{% 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>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="hint">Aucune variable pour l'instant.</p>
|
<p class="hint">Aucune variable pour l'instant.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dashboardCardFooter">
|
|
||||||
<a href="{{ url_for('game_variables', slug=game.slug) }}">Gérer les variables →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dangerZone" style="margin-top:24px;">
|
<div class="dangerZone dashDangerZone">
|
||||||
<h3>Paramètres du jeu</h3>
|
<h3>Paramètres du jeu</h3>
|
||||||
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}">
|
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}">
|
||||||
<label for="rename">Renommer le jeu</label>
|
<label for="rename">Renommer le jeu</label>
|
||||||
@@ -97,4 +202,23 @@
|
|||||||
<button type="submit" class="danger">🗑️ Supprimer ce jeu</button>
|
<button type="submit" class="danger">🗑️ Supprimer ce jeu</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function switchDashTab(tab) {
|
||||||
|
document.querySelectorAll('#dashTabs .builderTabBtn').forEach(function (btn) {
|
||||||
|
btn.classList.toggle('active', btn.dataset.tab === tab);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
|
||||||
|
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function toggleDashCreate(tab) {
|
||||||
|
var bar = document.getElementById('dashCreate-' + tab);
|
||||||
|
bar.style.display = bar.style.display === 'none' ? 'flex' : 'none';
|
||||||
|
if (bar.style.display === 'flex') {
|
||||||
|
var firstInput = bar.querySelector('input, select');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user