Files
Forge-Engine/templates/game_dashboard.html
T
williamandClaude Sonnet 5 e1216e99d5 Rend le panneau "Nouvel objet" compact — lignes de tableau, pas des cartes
Chaque champ prenait une grosse carte (~250px de haut : libellés Bulma
pleine taille, "Retirer ce champ" en texte) — inutilisable pour un objet
à 10+ champs, ce qui est pourtant l'usage visé par ce panneau.

Remplacé par une vraie ligne de tableau dense, sur le même principe que
"Champs existants" dans object_edit.html (déjà compact et sobre dans le
reste de l'outil) : une ligne = un champ, colonnes Nom/Type/Objet
lié/Mini/Maxi/Obligatoire, action "Retirer" réduite à une icône. Les
colonnes conditionnelles (Objet lié pour une relation, Mini/Maxi pour un
nombre) restent TOUJOURS présentes — sans quoi les colonnes de lignes
différentes ne s'aligneraient plus — seul leur contenu bascule entre le
vrai champ de saisie et un espace réservé "—", au lieu de masquer toute
la cellule comme avant.

object_form.js adapté en conséquence : bounds désormais 2 cibles
séparées (mini/maxi, chacune dans sa propre cellule) au lieu d'une seule
enveloppe commune, et chaque bascule s'accompagne de celle de son
espace réservé.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 11:04:13 +02:00

356 lines
16 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ game.name }} — Forge Engine{% endblock %}
{% block content_class %}content-wide{% endblock %}
{% block content %}
<!-- Une seule fenêtre, un onglet par catégorie (même principe que les
onglets "Écran / Logique / Timeline" de l'éditeur d'écran, voir
.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">
{% 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">
<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 %}
<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.</p>
{% endif %}
</div>
<!-- ---------- Objets ---------- -->
<div class="builderTabPanel" data-tab-panel="objects">
<div class="dashPanelHeader">
<h2>Objets</h2>
<button type="button" class="button is-small primary" onclick="openNewObjectPanel()">+ Nouvel objet</button>
</div>
{% if definitions %}
<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 %}
<tr>
<td><a href="{{ url_for('object_view', slug=game.slug, definition_id=d.id) }}">{{ d.name }}</a></td>
<td class="has-text-grey">{{ d.table_name }}</td>
<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 %}
</tbody>
</table>
</div>
{% else %}
<p class="hint">Aucun objet pour l'instant.</p>
{% endif %}
</div>
<!-- ---------- Éléments de jeu ---------- -->
<div class="builderTabPanel" data-tab-panel="element_types">
<div class="dashPanelHeader">
<h2>Éléments de jeu</h2>
<button type="button" class="button is-small primary" onclick="toggleDashCreate('element_types')">+ Créer un élément de jeu</button>
</div>
<form method="post" action="{{ url_for('element_types', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-element_types" style="display:none;">
<input type="text" name="icon" value="🧩" maxlength="4" style="max-width:60px;" title="Icône">
<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 %}
</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>
{% else %}
<p class="hint">Aucun élément de jeu pour l'instant.</p>
{% endif %}
</div>
<!-- ---------- Variables ---------- -->
<div class="builderTabPanel" data-tab-panel="variables">
<div class="dashPanelHeader">
<h2>Variables</h2>
<button type="button" class="button is-small primary" onclick="toggleDashCreate('variables')">+ Créer une variable</button>
</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 %}
</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>
{% else %}
<p class="hint">Aucune variable pour l'instant.</p>
{% endif %}
</div>
<div class="dangerZone dashDangerZone">
<h3>Paramètres du jeu</h3>
<form method="post" action="{{ url_for('game_rename', slug=game.slug) }}">
<label for="rename">Renommer le jeu</label>
<div class="fieldRow">
<input type="text" id="rename" name="game_name" value="{{ game.name }}" required autocomplete="off">
<button type="submit">Renommer</button>
</div>
</form>
<form method="post" action="{{ url_for('game_delete', slug=game.slug) }}" class="actionsRow"
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>
</form>
</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), mais avec un gabarit de ligne dense —
une vraie ligne de tableau (comme "Champs existants" dans
object_edit.html) plutôt qu'une grosse carte par champ — pensé pour
ajouter beaucoup de champs d'affilée sans que le panneau explose en
hauteur. -->
<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>
<div class="dashPanelHeader" style="margin-top:14px;">
<h2>Champs</h2>
<button type="button" class="button is-small" id="btnAddField">+ Ajouter un champ</button>
</div>
<div class="fieldsTableWrap">
<table class="table is-fullwidth is-hoverable fieldsTable">
<thead>
<tr><th>Nom</th><th>Type</th><th>Objet lié</th><th>Mini</th><th>Maxi</th><th>Obligatoire</th><th></th></tr>
</thead>
<tbody id="fieldsList"></tbody>
</table>
</div>
<div class="actionsRow"><button type="submit" class="primary">Créer l'objet</button></div>
</form>
</div>
</div>
<template id="fieldRowTemplate">
<tr>
<td><input class="input is-small" type="text" name="field_name[]" placeholder="ex. nom, pv..." required></td>
<td>
<div class="select is-small">
<select name="field_type[]" class="fieldTypeSelect">
{% for key, val in field_types.items() %}
<option value="{{ key }}">{{ val.label }}</option>
{% endfor %}
</select>
</div>
</td>
<td>
<div class="select is-small relationTarget" style="display:none;">
<select name="field_relation[]">
{% for d in definitions %}
<option value="{{ d.id }}">{{ d.name }}</option>
{% endfor %}
</select>
</div>
<span class="has-text-grey relationPlaceholder">—</span>
</td>
<td>
<input class="input is-small boundsTarget" type="number" step="any" name="field_min[]" placeholder="0" style="display:none;">
<span class="has-text-grey boundsPlaceholder">—</span>
</td>
<td>
<input class="input is-small boundsTarget" type="number" step="any" name="field_max[]" placeholder="100" style="display:none;">
<span class="has-text-grey boundsPlaceholder">—</span>
</td>
<td>
<div class="select is-small">
<select name="field_required[]">
<option value="0">Non</option>
<option value="1">Oui</option>
</select>
</div>
</td>
<td class="fieldsTableActions"><button type="button" class="button is-small is-danger btnRemoveField">🗑️</button></td>
</tr>
</template>
<script src="{{ url_for('static', filename='object_form.js') }}"></script>
<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();
}
}
// 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>
{% endblock %}