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:
+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>
|
||||
|
||||
Reference in New Issue
Block a user