Fix éditeur de scène 2D : décalage visuel des objets + crash Timeline
Deux bugs remontés en test manuel sur l'éditeur de scène (Phase A,
commit f0070fa) :
1. Décalage visuel à l'ajout d'un objet de scène (personnage/décor) :
render_scene_object.py positionne son <img> en absolu (left/top/
width/height en px), pensé pour être un enfant DIRECT de
.playScreen.playScene en mode jouable. Dans scene_edit.html, la même
balise est nichée dans .canvasElementInner, lui-même déjà positionné
par .canvasElement — l'image se repositionnait donc EN PLUS depuis
#canvas (son ancêtre positionné le plus proche), en double du
décalage déjà appliqué par le conteneur. Corrigé par une règle CSS
scoped à l'éditeur (.canvasElementInner > img) qui neutralise le
positionnement propre de l'image et la fait simplement remplir son
conteneur.
2. "FOREIGN KEY constraint failed" à l'ajout d'un clip de Timeline sur
un objet de scène : _animation_clips.element_id portait une VRAIE
contrainte FK vers _screen_elements depuis la création de la table.
animation-timeline.js est réutilisé TEL QUEL entre les deux éditeurs
(voir le plan "Fondations d'une plateforme multi-éditeurs") et
n'opère aucune distinction — pour un jeu jeu_2d, element_id désigne
en réalité un id de _scene_objects, absent de _screen_elements, d'où
l'échec de l'INSERT sous PRAGMA foreign_keys=ON. ensure_animation_schema
reconstruit maintenant la table (une fois, migration automatique à la
volée comme le reste du schéma) sans cette contrainte FK — même
patron que trigger_element_id/cond_element_a dans ensure_flow_schema.py.
Comme la suppression en cascade reposait jusqu'ici sur cette FK, un
nettoyage manuel des clips a été ajouté à la suppression d'un élément
(delete_element.py) et d'un objet de scène (delete_scene_object.py).
Nouveaux tests (tests/test_scene_edit_view.py, +4 cas) : ajout d'un
clip de Timeline sur un objet de scène via la route, nettoyage des
clips à la suppression d'un objet de scène et d'un élément DOM. Suite
complète : 312 tests passent (aucune régression).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f0070faced
commit
43abfc9b93
@@ -1,29 +1,63 @@
|
||||
import db
|
||||
|
||||
_CREATE_TABLE = """
|
||||
CREATE TABLE _animation_clips (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE,
|
||||
element_id INTEGER NOT NULL,
|
||||
kind TEXT NOT NULL DEFAULT 'animate_css',
|
||||
animate_name TEXT,
|
||||
custom_keyframes TEXT,
|
||||
start_time REAL NOT NULL DEFAULT 0,
|
||||
duration REAL NOT NULL DEFAULT 1,
|
||||
delay REAL NOT NULL DEFAULT 0,
|
||||
easing TEXT NOT NULL DEFAULT 'ease',
|
||||
iteration_count TEXT NOT NULL DEFAULT '1',
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
def ensure_animation_schema(slug):
|
||||
"""Crée la table des clips d'animation si elle n'existe pas encore —
|
||||
même principe que ensure_flow_schema.py : un jeu créé avant l'ajout de
|
||||
cette fonctionnalité n'a pas besoin d'être migré à la main, la première
|
||||
ouverture du panneau timeline suffit."""
|
||||
ouverture du panneau timeline suffit.
|
||||
|
||||
element_id désigne, selon game_type, un _screen_elements (éditeur
|
||||
document) OU un _scene_objects (éditeur jeu_2d) — animation-timeline.js
|
||||
est réutilisé TEL QUEL entre les deux éditeurs (voir le plan "Fondations
|
||||
d'une plateforme multi-éditeurs") et ne fait aucune distinction. Pas de
|
||||
contrainte FK vers _screen_elements : une scène jeu_2d n'a pas de ligne
|
||||
correspondante dans cette table, ce qui ferait échouer l'insertion avec
|
||||
PRAGMA foreign_keys=ON (voir db/connection.py). Le nettoyage à la
|
||||
suppression d'un élément/objet est fait manuellement (voir
|
||||
screens/elements/delete_element.py et screens/scenes/delete_scene_object.py),
|
||||
même patron que trigger_element_id/cond_element_a dans
|
||||
ensure_flow_schema.py."""
|
||||
conn = db.connect(slug)
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS _animation_clips (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE,
|
||||
element_id INTEGER NOT NULL REFERENCES _screen_elements(id) ON DELETE CASCADE,
|
||||
kind TEXT NOT NULL DEFAULT 'animate_css',
|
||||
animate_name TEXT,
|
||||
custom_keyframes TEXT,
|
||||
start_time REAL NOT NULL DEFAULT 0,
|
||||
duration REAL NOT NULL DEFAULT 1,
|
||||
delay REAL NOT NULL DEFAULT 0,
|
||||
easing TEXT NOT NULL DEFAULT 'ease',
|
||||
iteration_count TEXT NOT NULL DEFAULT '1',
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
)
|
||||
cols = {r["name"]: r for r in conn.execute("PRAGMA table_info(_animation_clips)").fetchall()}
|
||||
if not cols:
|
||||
conn.executescript(_CREATE_TABLE)
|
||||
elif cols["element_id"]["notnull"] and _has_fk_on_element_id(conn):
|
||||
conn.executescript(
|
||||
_CREATE_TABLE.replace("CREATE TABLE _animation_clips", "CREATE TABLE _animation_clips_new")
|
||||
)
|
||||
conn.execute(
|
||||
"""INSERT INTO _animation_clips_new
|
||||
(id, screen_id, element_id, kind, animate_name, custom_keyframes,
|
||||
start_time, duration, delay, easing, iteration_count, created_at)
|
||||
SELECT id, screen_id, element_id, kind, animate_name, custom_keyframes,
|
||||
start_time, duration, delay, easing, iteration_count, created_at
|
||||
FROM _animation_clips"""
|
||||
)
|
||||
conn.execute("DROP TABLE _animation_clips")
|
||||
conn.execute("ALTER TABLE _animation_clips_new RENAME TO _animation_clips")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _has_fk_on_element_id(conn):
|
||||
return any(
|
||||
fk["from"] == "element_id" for fk in conn.execute("PRAGMA foreign_key_list(_animation_clips)").fetchall()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user