Onboarding guidé systématique + tableau de bord simplifié
Comportement SYSTÉMATIQUE à chaque création de jeu (admin compris), pas une formalité réservée à l'inscription : /onboarding (routes/onboarding/) devient le point d'entrée unique — 4 cartes retournables (survol = explication au dos), défilement horizontal animé vers le nom du jeu. Un admin y repasse à volonté (pas de project_slug dédié, jamais bloqué/ redirigé vers un projet précédent) ; un compte "user" n'en a plus qu'un créé d'office (routes/auth/register_2fa.py), guidé ici à la place. - db/games/game_type_catalog.py : catalogue des 4 types (Quiz/ Embranchement-escape game/RPG/Créer mon jeu de A à Z), _meta['onboarding_type'] décide du "kind" du premier écran créé et si le tableau de bord complet reste accessible. - routes/games/game_dashboard.py, templates/game_dashboard_simple.html : un type restreint (quiz/embranchement/rpg) voit désormais SON tableau de bord (même route que "custom"), rendu en version simplifiée — juste ses écrans en cartes avec un aperçu RÉEL du contenu (scène mise à l'échelle par container query CSS, adaptée à la largeur réelle de la carte). "+ Ajouter un écran" n'y propose pas de choix de type : imposé par le projet (routes/screens/screens_new.py), verrouillé aussi côté serveur. - core/auth_guard.py : plus de blocage de game_dashboard par type — la restriction se fait au rendu, pas à l'accès à la route. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0f23024882
commit
76a50fa87a
+13
-1
@@ -20,7 +20,7 @@ exécute un vrai CREATE TABLE, et remplir le formulaire généré exécute un
|
||||
vrai INSERT dans cette table.
|
||||
"""
|
||||
|
||||
from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES
|
||||
from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES, SCORE_STATUS_CHOICES, SCORE_STATUS_LABELS
|
||||
from .slugify import slugify
|
||||
from .table_name_for import table_name_for
|
||||
from .game_dir import game_dir
|
||||
@@ -36,6 +36,11 @@ from .games.create_game import create_game
|
||||
from .games.update_game_name import update_game_name
|
||||
from .games.delete_game import delete_game
|
||||
from .games.move_game import move_game
|
||||
from .games.game_type_catalog import (
|
||||
ONBOARDING_TYPES, DEFAULT_ONBOARDING_TYPE,
|
||||
get_onboarding_type, get_onboarding_type_raw, set_onboarding_type,
|
||||
is_restricted,
|
||||
)
|
||||
|
||||
from .definitions.list_definitions import list_definitions
|
||||
from .definitions.get_definition import get_definition
|
||||
@@ -66,6 +71,10 @@ from .global_vars.update_global_variable import update_global_variable
|
||||
from .global_vars.delete_global_variable import delete_global_variable
|
||||
from .global_vars.delete_global_variable_by_id import delete_global_variable_by_id
|
||||
|
||||
from .scoring.get_score import get_score
|
||||
from .scoring.set_score_value import set_score_value
|
||||
from .scoring.set_status import set_status
|
||||
|
||||
from .custom_events.list_custom_events import list_custom_events
|
||||
from .custom_events.get_custom_event import get_custom_event
|
||||
from .custom_events.create_custom_event import create_custom_event
|
||||
@@ -73,9 +82,12 @@ from .custom_events.update_custom_event import update_custom_event
|
||||
|
||||
__all__ = [
|
||||
"PROJECTS_DIR", "FIELD_TYPES", "GLOBAL_VARIABLE_TYPES", "PLAYER_SHARED",
|
||||
"SCORE_STATUS_CHOICES", "SCORE_STATUS_LABELS", "get_score", "set_score_value", "set_status",
|
||||
"slugify", "table_name_for", "game_dir", "db_path", "connect",
|
||||
"list_games", "game_meta", "create_game", "update_game_name", "delete_game", "move_game",
|
||||
"is_public_played", "set_public_played", "get_game_type", "DEFAULT_GAME_TYPE",
|
||||
"ONBOARDING_TYPES", "DEFAULT_ONBOARDING_TYPE", "get_onboarding_type",
|
||||
"get_onboarding_type_raw", "set_onboarding_type", "is_restricted",
|
||||
"list_definitions", "get_definition", "create_definition", "rename_definition",
|
||||
"definitions_referencing", "add_field_to_definition", "update_field", "delete_field",
|
||||
"delete_definition",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from ..connection import connect
|
||||
from .is_public_played import is_public_played
|
||||
from .get_game_type import get_game_type
|
||||
from .game_type_catalog import get_onboarding_type
|
||||
|
||||
|
||||
def game_meta(slug):
|
||||
@@ -14,8 +14,11 @@ def game_meta(slug):
|
||||
# ligne" en a besoin pour afficher son état actuel), sans avoir à
|
||||
# appeler is_public_played(slug) séparément dans chaque route.
|
||||
"is_public_played": is_public_played(slug),
|
||||
# Type de jeu (fondations multi-éditeurs) : "document" (éditeur
|
||||
# générique) ou "jeu_2d" (éditeur de scène) — voir
|
||||
# get_game_type.py et routes/screens/screen_edit.py.
|
||||
"game_type": get_game_type(slug),
|
||||
# Onboarding guidé (voir game_type_catalog.py) : utilisé par
|
||||
# templates/base.html pour cacher le lien "Tableau de bord" à un
|
||||
# compte "restreint" (quiz/embranchement/rpg).
|
||||
"onboarding_type": get_onboarding_type(slug),
|
||||
# Pas de "game_type" ici : depuis la fusion des moteurs (voir
|
||||
# screens/screens_repo/ensure_schema.py), "document"/"jeu_2d" est
|
||||
# une propriété PAR ÉCRAN (screen["kind"]), plus du projet.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Onboarding guidé (voir routes/onboarding/onboarding_new.py) : les 4
|
||||
choix proposés à CHAQUE création de projet — PAS un 3e type de moteur
|
||||
(voir screens/screens_repo/ensure_schema.py, fusion des moteurs :
|
||||
"document"/"jeu_2d" est une propriété PAR ÉCRAN), une classification
|
||||
produit posée par-dessus. Chaque choix décide du "kind" du tout premier
|
||||
écran créé (screen_kind ci-dessous, aussi imposé à tout écran ajouté
|
||||
ensuite — voir routes/screens/screens_new.py) et si le tableau de bord
|
||||
complet (game_dashboard, objets/éléments de jeu/variables) reste
|
||||
accessible ensuite, ou seulement sa version simplifiée (juste les écrans
|
||||
en cartes — voir routes/games/game_dashboard.py, restricted).
|
||||
|
||||
_meta['onboarding_type'] (une ligne _meta par projet, même convention que
|
||||
is_public_played/l'ancien game_type) retient CE choix — jamais lu par le
|
||||
rendu jouable, seulement par le routage (routes/games/game_dashboard.py,
|
||||
routes/screens/screens_new.py)."""
|
||||
import db
|
||||
|
||||
DEFAULT_ONBOARDING_TYPE = "custom"
|
||||
|
||||
ONBOARDING_TYPES = {
|
||||
"quiz": {
|
||||
"label": "Quiz",
|
||||
"tagline": "Questions, réponses, score.",
|
||||
"icon": "🎯",
|
||||
"description": "Gérer des questions/réponses, avec score.",
|
||||
"screen_kind": "document",
|
||||
"restricted": True,
|
||||
},
|
||||
"embranchement": {
|
||||
"label": "Jeu à embranchement / escape game",
|
||||
"tagline": "Chaque choix a une conséquence.",
|
||||
"icon": "🗝️",
|
||||
"description": (
|
||||
"Former à travers le jeu sur des concepts métier avec des "
|
||||
"conséquences observables ; réaliser des escape games sous "
|
||||
"forme d'énigmes."
|
||||
),
|
||||
"screen_kind": "document",
|
||||
"restricted": True,
|
||||
},
|
||||
"rpg": {
|
||||
"label": "Jeu RPG",
|
||||
"tagline": "Ton monde, ton héros, ton scénario.",
|
||||
"icon": "🗺️",
|
||||
"description": "Réaliser des serious games scénarisés sous forme de RPG 2D sur navigateur.",
|
||||
"screen_kind": "jeu_2d",
|
||||
"restricted": True,
|
||||
},
|
||||
"custom": {
|
||||
"label": "Créer mon jeu de A à Z",
|
||||
"tagline": "Toute la puissance du moteur, sans limite.",
|
||||
"icon": "⚡",
|
||||
"description": (
|
||||
"Pour les utilisateurs avec des notions de dev/jeu vidéo : "
|
||||
"accès à toute la puissance du moteur."
|
||||
),
|
||||
"screen_kind": "document",
|
||||
"restricted": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_onboarding_type_raw(slug):
|
||||
"""None si _meta['onboarding_type'] est absent — distingue un projet
|
||||
JAMAIS passé par l'onboarding guidé (créé avant son existence, ou par
|
||||
l'admin via "+ Nouveau jeu") d'un projet explicitement "custom"."""
|
||||
conn = db.connect(slug)
|
||||
row = conn.execute("SELECT value FROM _meta WHERE key = 'onboarding_type'").fetchone()
|
||||
conn.close()
|
||||
return row["value"] if row else None
|
||||
|
||||
|
||||
def get_onboarding_type(slug):
|
||||
return get_onboarding_type_raw(slug) or DEFAULT_ONBOARDING_TYPE
|
||||
|
||||
|
||||
def set_onboarding_type(slug, onboarding_type):
|
||||
conn = db.connect(slug)
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO _meta (key, value) VALUES ('onboarding_type', ?)",
|
||||
(onboarding_type,),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def is_restricted(onboarding_type_or_slug):
|
||||
"""Accepte directement une clé de ONBOARDING_TYPES, ou un slug de
|
||||
projet (résout alors son onboarding_type d'abord) — pratique aussi
|
||||
bien pour core/auth_guard.py (a le slug) que pour un test unitaire (a
|
||||
déjà la clé)."""
|
||||
onboarding_type = (
|
||||
onboarding_type_or_slug if onboarding_type_or_slug in ONBOARDING_TYPES
|
||||
else get_onboarding_type(onboarding_type_or_slug)
|
||||
)
|
||||
return ONBOARDING_TYPES.get(onboarding_type, ONBOARDING_TYPES[DEFAULT_ONBOARDING_TYPE])["restricted"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user