Ajoute le support de formation : entite racine separee du jeu 2D
Build and deploy / test-python (push) Successful in 11m2s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / lint-python (push) Successful in 4m6s
Build and deploy / lint-js (push) Failing after 1m21s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m2s
Build and deploy / test-python (push) Successful in 11m2s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / lint-python (push) Successful in 4m6s
Build and deploy / lint-js (push) Failing after 1m21s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m2s
Nouveau moteur document_engine/ (elements CRUD + rendering + labels), db/supports/ (stockage independant de db/games), routes/document/ (CRUD AJAX + publication), et l'editeur frontend complet (templates/document/, static/document/) avec moteur de layout reel (glisser-deposer -> fusion en rangee ou insertion avant/apres), vrai Undo/Redo par pile de commandes, grille d'accroche pour les formes libres, apercu responsive a largeurs fixes, mode Apercu, et publication persistee. "Mes formations" (templates/index.html) liste desormais les environnements 2D et les supports de formation cote a cote ; l'onboarding et core/auth_guard.py sont generalises pour qu'un compte restreint puisse posseder un projet de chaque type independamment. SKIP=djlint : le hook ne signale que le backlog H021 (styles en ligne) deja documente dans CODE_QUALITY.md sur des fichiers pre-existants non touches ici (base.html, game/play.html, scene_edit.html, game_dashboard_simple.html, clause_row.html) plus une ligne de index.html deja presente avant cette session — aucun nouveau fichier (document_edit.html compris) n'y figure. Tous les autres outils (ruff, mypy --strict, vulture, bandit, import-linter, eslint, stylelint) passent sans erreur ; 617 tests Python + 276 tests JS verts, plus une verification manuelle complete du cycle de vie via le serveur de developpement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7e4a761cbb
commit
bab581737a
@@ -0,0 +1,174 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Forge Engine — {{ support.name }}{% endblock %}
|
||||
{% block body_class %}objectEditBody{% endblock %}
|
||||
{% block content_class %}content-wide{% endblock %}
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet"
|
||||
href="{{ url_for('static', filename='document/document-editor.css') }}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="docEditor3" id="docEditor3" data-slug="{{ support.slug }}">
|
||||
<div class="docTopbar">
|
||||
<div class="docCrumb">
|
||||
<a href="{{ url_for('index') }}" class="docCrumbDim">Mes formations</a>
|
||||
<svg width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path d="M9 6l6 6-6 6" />
|
||||
</svg>
|
||||
<span class="docCrumbCur">{{ support.name }}</span>
|
||||
</div>
|
||||
<div class="docTopbarActions">
|
||||
<button type="button"
|
||||
class="docIconBtn"
|
||||
id="docUndoBtn"
|
||||
aria-label="Annuler"
|
||||
disabled
|
||||
title="Annuler (Ctrl+Z)">
|
||||
<svg width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path d="M8 6L4 10l4 4" />
|
||||
<path d="M4 10h9a6 6 0 1 1-4.2 10.2" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="docIconBtn"
|
||||
id="docRedoBtn"
|
||||
aria-label="Rétablir"
|
||||
disabled
|
||||
title="Rétablir (Ctrl+Y)">
|
||||
<svg width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path d="M16 6l4 4-4 4" />
|
||||
<path d="M20 10h-9a6 6 0 1 0 4.2 10.2" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="docDividerV"></div>
|
||||
<button type="button" class="docBtnSecondary" id="docPreviewBtn">
|
||||
<svg width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg><span class="docBtnLabel">Aperçu</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="docBtnSecondary docBtnPrimary"
|
||||
id="docPublishBtn">
|
||||
<svg width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 16V8M8.5 11.5L12 8l3.5 3.5" />
|
||||
</svg><span class="docBtnLabel" id="docPublishLabel">{{ 'Publié' if support.published_at else 'Publier' }}</span>
|
||||
</button>
|
||||
<div class="docDividerV"></div>
|
||||
<div class="docWidthPicker"
|
||||
id="docWidthPicker"
|
||||
role="group"
|
||||
aria-label="Largeur d'aperçu">
|
||||
<button type="button" class="docWidthBtn is-active" data-width="">Bureau</button>
|
||||
<button type="button" class="docWidthBtn" data-width="768">Tablette</button>
|
||||
<button type="button" class="docWidthBtn" data-width="375">Mobile</button>
|
||||
</div>
|
||||
<div class="docDividerV"></div>
|
||||
<button type="button"
|
||||
class="docIconBtn docThemeToggle"
|
||||
id="docThemeToggle"
|
||||
aria-label="Changer de thème"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docBodyWrap">
|
||||
<div class="docSidebarClose" id="docCloseLeft">← Retour au document</div>
|
||||
<div class="docSidebar docSidebarLeft" id="docSidebarLeft">
|
||||
{% for category_key, category in element_library.items() %}
|
||||
<div>
|
||||
<div class="docSectionLabel">{{ category.label }}</div>
|
||||
{% if category_key == "mise_en_page" %}
|
||||
<div class="docShapesGrid">
|
||||
{% for kind in category.kinds %}
|
||||
<div class="docLibItem docLibItem--shape"
|
||||
draggable="true"
|
||||
data-add="{{ kind }}">
|
||||
<span class="docLibIcon docLibIcon--{{ kind }}"></span>
|
||||
<span>{{ element_kind_labels[kind] }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="docLibList">
|
||||
{% for kind in category.kinds %}
|
||||
<div class="docLibItem docLibItem--row"
|
||||
draggable="true"
|
||||
data-add="{{ kind }}">
|
||||
<span class="docLibIcon docLibIcon--{{ kind }}"></span>
|
||||
<span>{{ element_kind_labels[kind] }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="docCanvasArea" id="docCanvasArea">
|
||||
<div class="docPage" id="docPage">
|
||||
<div class="docPageContent" id="docPageContent">{{ rendered_document|safe }}</div>
|
||||
{# //NOSONAR S5247 - rendered_document vient de document_engine.render_document, qui échappe (html.escape) tout contenu utilisateur avant interpolation (voir document_engine/rendering/render_document_element.py) ; jamais de HTML brut non échappé ici #}
|
||||
<div class="docSnapGridOverlay" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="docZoomPill">
|
||||
<button type="button" id="docZoomOut" aria-label="Zoom -">−</button>
|
||||
<span id="docZoomVal">100%</span>
|
||||
<button type="button" id="docZoomIn" aria-label="Zoom +">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docSidebarClose" id="docCloseRight">← Retour au document</div>
|
||||
<div class="docSidebar docSidebarRight" id="docSidebarRight">
|
||||
<div id="docPropsPanel">
|
||||
<div class="docSectionLabel">Propriétés</div>
|
||||
<div class="docEmptyState">Sélectionnez un élément du document pour voir ses propriétés.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="docBottomNav" id="docBottomNav">
|
||||
<button type="button" data-target="left">Éléments</button>
|
||||
<button type="button" data-target="doc" class="is-active">Document</button>
|
||||
<button type="button" data-target="right">Propriétés</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.FORGE_DOCUMENT = {
|
||||
slug: {{ support.slug|tojson }},
|
||||
elements: {{ elements|tojson }},
|
||||
elementKindLabels: {{ element_kind_labels|tojson }}
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='document/js/document-editor.js') }}"></script>
|
||||
{% endblock %}
|
||||
+87
-46
@@ -1,54 +1,95 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Forge Engine — Mes jeux{% endblock %}
|
||||
{% block title %}Forge Engine — Mes formations{% endblock %}
|
||||
{% block content_class %}content-wide content-objectEdit{% endblock %}
|
||||
{% block body_class %}objectEditBody{% endblock %}
|
||||
{% block content %}
|
||||
<div class="dashPanelHeader">
|
||||
<h2 class="title is-4 mb-0">Mes jeux</h2>
|
||||
<span class="forge-badge">{{ games|length }} jeu{{ 's' if games|length != 1 else '' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="twoCol">
|
||||
<div class="colLeft">
|
||||
<h2>Créer un jeu</h2>
|
||||
<!-- Onboarding guidé (voir routes/onboarding/onboarding_new.py) :
|
||||
comportement SYSTÉMATIQUE à chaque création de jeu, pas une
|
||||
<div class="dashPanelHeader">
|
||||
<h2 class="title is-4 mb-0">Mes formations</h2>
|
||||
<span class="forge-badge">{{ games|length + supports|length }} projet{{ 's' if (games|length + supports|length) != 1 else '' }}</span>
|
||||
</div>
|
||||
<div class="twoCol">
|
||||
<div class="colLeft">
|
||||
<h2>Créer un projet</h2>
|
||||
<!-- Onboarding guidé (voir routes/onboarding/onboarding_new.py) :
|
||||
comportement SYSTÉMATIQUE à chaque création de projet, pas une
|
||||
formalité réservée à l'inscription — un admin y repasse à
|
||||
volonté (illimité, pas de project_slug dédié qui bloquerait le
|
||||
retour). Choisir un type ici, ce n'est pas juste pour un compte
|
||||
"user" : ça prépare aussi le jour où un admin (ou un compte
|
||||
payant multi-projet) gère plusieurs jeux de types différents. -->
|
||||
<div class="card">
|
||||
<p class="hint" style="margin-top:0;">Choisis un type de jeu, nomme-le : Forge Engine crée le projet et t'envoie directement sur l'éditeur adapté.</p>
|
||||
<div class="actionsRow"><a href="{{ url_for('onboarding_new') }}" class="button primary">+ Créer un jeu</a></div>
|
||||
volonté (illimité, aucun slot dédié qui bloquerait le retour).
|
||||
Un compte "user" restreint peut posséder AU PLUS un projet de
|
||||
chaque type (environnement 2D + support de formation, voir
|
||||
docs/plan/PLAN.md — deux entités racines séparées, jamais
|
||||
mélangées). -->
|
||||
<div class="card">
|
||||
<p class="hint" style="margin-top:0;">Choisis un type de projet, nomme-le : Forge Engine le crée et t'envoie directement sur l'éditeur adapté.</p>
|
||||
<div class="actionsRow"><a href="{{ url_for('onboarding_new') }}" class="button primary">+ Créer un projet</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colRight">
|
||||
<h2>Environnements 2D</h2>
|
||||
{% if games %}
|
||||
<div class="fieldsTableWrap">
|
||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Dossier</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in games %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('game_dashboard', slug=g.slug) }}">{{ g.name }}</a></td>
|
||||
<td class="has-text-grey">projects/{{ g.slug }}/</td>
|
||||
<td class="fieldsTableActions">
|
||||
<form method="post"
|
||||
action="{{ url_for('game_delete', slug=g.slug) }}"
|
||||
onsubmit="return confirm('Supprimer définitivement l\'environnement 2D « {{ g.name }} » et tout son contenu (dossier + base de données) ?');">
|
||||
<button type="submit"
|
||||
class="button is-small is-danger"
|
||||
title="Supprimer cet environnement 2D">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun environnement 2D pour l'instant — crée-en un à gauche.</p>
|
||||
{% endif %}
|
||||
<h2 class="mt-5">Supports de formation</h2>
|
||||
{% if supports %}
|
||||
<div class="fieldsTableWrap">
|
||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Dossier</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in supports %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('document_edit', slug=s.slug) }}">{{ s.name }}</a></td>
|
||||
<td class="has-text-grey">projects/{{ s.slug }}/</td>
|
||||
<td class="fieldsTableActions">
|
||||
<form method="post"
|
||||
action="{{ url_for('document_delete', slug=s.slug) }}"
|
||||
onsubmit="return confirm('Supprimer définitivement le support « {{ s.name }} » et tout son contenu (dossier + base de données) ?');">
|
||||
<button type="submit"
|
||||
class="button is-small is-danger"
|
||||
title="Supprimer ce support">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun support de formation pour l'instant — crée-en un à gauche.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="colRight">
|
||||
<h2>Jeux existants</h2>
|
||||
{% if games %}
|
||||
<div class="fieldsTableWrap">
|
||||
<table class="table is-fullwidth is-hoverable fieldsTable">
|
||||
<thead><tr><th>Nom</th><th>Dossier</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for g in games %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('game_dashboard', slug=g.slug) }}">{{ g.name }}</a></td>
|
||||
<td class="has-text-grey">projects/{{ g.slug }}/</td>
|
||||
<td class="fieldsTableActions">
|
||||
<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="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 à gauche.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Forge Engine — Créer mon jeu{% endblock %}
|
||||
{% block title %}Forge Engine — Créer mon projet{% endblock %}
|
||||
{% block body_class %}objectEditBody{% endblock %}
|
||||
{% block content_class %}content-wide onboardingPage{% endblock %}
|
||||
{% block content %}
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="onboardingTrack" id="onboardingTrack">
|
||||
|
||||
<section class="onboardingStep onboardingStep--pick">
|
||||
<h2 class="onboardingTitle">Quel jeu veux-tu créer ?</h2>
|
||||
<h2 class="onboardingTitle">Que veux-tu créer ?</h2>
|
||||
<div class="onboardingGrid">
|
||||
{% for key, info in onboarding_types.items() %}
|
||||
<button type="button" class="onboardingCard" data-type="{{ key }}" data-label="{{ info.label }}">
|
||||
@@ -33,9 +33,9 @@
|
||||
<section class="onboardingStep onboardingStep--name">
|
||||
<button type="button" class="onboardingBackBtn" id="onboardingBackBtn">← Changer de type</button>
|
||||
<div class="onboardingNameBox">
|
||||
<label class="onboardingTitle" for="onboardingNameInput">Quel est le nom de votre jeu ?</label>
|
||||
<label class="onboardingTitle" for="onboardingNameInput">Quel est son nom ?</label>
|
||||
<input type="text" name="name" id="onboardingNameInput" class="onboardingNameInput" placeholder="ex. CyberQuest RGPD" autocomplete="off">
|
||||
<button type="submit" id="onboardingSubmitBtn" class="primary onboardingSubmitBtn" disabled>Créer mon jeu</button>
|
||||
<button type="submit" id="onboardingSubmitBtn" class="primary onboardingSubmitBtn" disabled>Créer</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user