// ---------- Éditeur de scène 2D (fondations multi-éditeurs) ---------- // JS propre à templates/scene_edit.html : ajout/déplacement/redimension- // nement d'objets de scène EN PIXELS (pas de %, voir ensure_scene_schema.py), // mirror des fonctions équivalentes de static/js/screen_edit/tree-panels.js // pour l'éditeur document (onElementMouseDown/onResizeMouseDown/ // saveGeometry), mais sur une scène à taille FIXE plutôt qu'un canevas // fluide — la logique de flow elle-même (flow-editor.js, tabs-and-blocks.js, // animation-timeline.js, personnage-panel.js, personnage-preview.js) est // réutilisée TELLE QUELLE, chargée telle quelle par scene_edit.html. // AJAX (voir routes/scenes/scene_object_add.py — répond en JSON dès que // "X-Requested-With: fetch" est posé) : pose l'objet SANS recharger la // page, pour ne plus faire "disparaître" un instant l'éditeur de collision/ // quêtes (état JS entier réinitialisé par un rechargement complet) ni // perdre l'onglet/le scroll en cours. Le nouvel objet n'est PAS // automatiquement sélectionné/ouvert dans le panneau de propriétés (voir // onSceneObjectMouseDown, qui recharge encore la page pour ça) — juste // posé sur le canevas, cliquable ensuite comme n'importe quel autre. function addSceneObject(kind, forgeCharacter, backgroundSlug, assetId, x, y) { const body = new FormData(); body.append('kind', kind); if (forgeCharacter) body.append('forge_character', forgeCharacter); if (backgroundSlug) body.append('background_slug', backgroundSlug); // "Mes assets" (voir auth/user_assets_dir.py) : une image importée par // le créateur ou générée par l'IA, distincte de BACKGROUND_LIBRARY. if (assetId) body.append('asset_id', assetId); // Vrai glisser-déposer (voir onCanvasDrop ci-dessous) : position EXACTE // du dépôt — absent pour un simple clic sur la galerie, qui garde le // défaut habituel (100,100, voir screens/scenes/add_scene_object.py). if (x != null && y != null) { body.append('x', x); body.append('y', y); } fetch(`/game/${GAME_SLUG}/screens/${SCREEN_ID}/scene-objects/add`, { method: 'POST', headers: { 'X-Requested-With': 'fetch' }, body, }) .then((r) => { // Secours si le serveur répond encore par une redirection HTML // classique (ex. déploiement pas encore à jour côté serveur alors // que ce script, lui, l'est déjà) : l'objet est de toute façon déjà // posé en base à ce stade (voir screens/scenes/add_scene_object.py), // seul un rechargement peut alors l'afficher. const contentType = r.headers.get('content-type') || ''; if (!r.ok || !contentType.includes('application/json')) { window.location.reload(); return null; } return r.json(); }) .then((json) => { if (!json) return; const canvas = document.getElementById('canvas'); const el = document.createElement('div'); el.className = 'canvasElement'; el.id = `el-${json.id}`; el.style.position = 'absolute'; el.style.left = `${json.x}px`; el.style.top = `${json.y}px`; el.style.width = `${json.width}px`; el.style.height = `${json.height}px`; el.setAttribute('onmousedown', `onSceneObjectMouseDown(event, ${json.id})`); el.innerHTML = `
${json.rendered_html}
` + `
`; canvas.appendChild(el); if (json.animations) { ELEMENT_ANIMATIONS_MAP[json.id] = json.animations; restartPersonnageIdlePreview(json.id, json.animations); } }) // Filet de sécurité : mieux vaut un rechargement (l'objet est déjà en // base à ce stade) qu'un bouton "+ Ajouter" qui semble ne plus rien // faire du tout si jamais une erreur imprévue survient ici. .catch(() => { window.location.reload(); }); } // ---------- Vrai glisser-déposer depuis la galerie (demande explicite : // "n'importe où sur la scène", pas seulement un clic qui pose à une // position par défaut) ---------- // dataTransfer ne porte que des CHAÎNES : les 4 arguments d'addSceneObject // sont sérialisés en JSON plutôt qu'éclatés en plusieurs champs (certains // sont `null` — JSON les distingue proprement d'une chaîne vide, un // simple attribut HTML ne le pourrait pas). `draggable="true"` sur un // '; const keyInput = row.querySelector('.personnageKeyCapture'); bindPersonnageKeyCapture(keyInput); keyInput.addEventListener('change', saveSceneObjectCommands); row.querySelector('.cmdExtraAnim').addEventListener('change', saveSceneObjectCommands); return row; } function addPersonnageCommandExtraRow() { const body = document.getElementById('personnageCommandsBody'); const container = document.getElementById('personnageCommandsExtra'); if (!body || !container) return; const objectId = Number.parseInt(body.dataset.elementId, 10); container.appendChild(personnageCommandExtraRowEl(objectId, '', '')); } // "ℹ️ Informations" (voir templates/scene_edit.html) : le nom d'un // personnage, réutilisé comme "qui parle" dans l'éditeur de dialogue d'un // déclencheur (voir static/js/triggers/trigger-editor.js). function saveSceneObjectName(objectId, name) { fetch(`/game/${GAME_SLUG}/scene-objects/${objectId}/name`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }), }).then(refreshSceneObjectNames); } // SCENE_OBJECT_NAMES (voir routes/scenes/scene_object_names_list.py) : // posée UNE SEULE FOIS au chargement complet de la page (