// ---------- Panneau de propriétés "🎭 Personnage" (Phase 8) ---------- // Panneau sur-mesure (custom_panel, voir registry.py) : PAS de soumission // générique via #propsForm — chaque changement s'enregistre en AJAX vers // POST /game//elements//personnage (element_set_personnage_data.py), // qui renvoie les animations résolues pour relancer immédiatement l'aperçu // animé (voir personnage-preview.js) sans recharger la page. function _personnageElementId() { var body = document.getElementById('personnagePanelBody'); return body ? parseInt(body.dataset.elementId, 10) : null; } function _savePersonnageData(data) { var elementId = _personnageElementId(); if (!elementId) return Promise.resolve(); return fetch('/game/' + GAME_SLUG + '/elements/' + elementId + '/personnage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) .then(function (r) { return r.json(); }) .then(function (json) { if (json.animations) { // Reste en phase avec le sélecteur d'animation du nœud de flow/clip // de Timeline (voir flow-editor.js/animation-timeline.js), sans // recharger la page. ELEMENT_ANIMATIONS_MAP[elementId] = json.animations; restartPersonnageIdlePreview(elementId, json.animations); } return json; }); } // ---------- Source Forge : changer de personnage ---------- function swapPersonnageCharacter(forgeCharacter) { document.querySelectorAll('#personnageGallerySwap .iconGalleryTile').forEach(function (tile) { tile.classList.toggle('selected', tile.getAttribute('onclick') === "swapPersonnageCharacter('" + forgeCharacter + "')"); }); _savePersonnageData({ source: 'forge', forge_character: forgeCharacter }); } // ---------- Source personnalisée : animations importées ---------- function _collectPersonnageAnimations() { var animations = {}; document.querySelectorAll('#personnageAnimationsList .personnageAnimRow').forEach(function (row) { var name = row.querySelector('.personnageAnimName').value.trim(); if (!name) return; var frames = Array.prototype.map.call(row.querySelectorAll('.personnageFrameThumb'), function (img) { return img.src; }); animations[name] = frames; }); return animations; } function _saveCustomAnimations() { return _savePersonnageData({ source: 'custom', animations: _collectPersonnageAnimations() }); } function addPersonnageAnimation() { var list = document.getElementById('personnageAnimationsList'); var row = document.createElement('div'); row.className = 'personnageAnimRow box'; row.style.marginBottom = '8px'; row.style.padding = '8px'; row.innerHTML = '
' + '' + '' + '
' + '
' + '' + ''; list.appendChild(row); } function removePersonnageAnimation(btn) { btn.closest('.personnageAnimRow').remove(); _saveCustomAnimations(); } // Délégation d'événements : les lignes d'animation sont ajoutées // dynamiquement (addPersonnageAnimation ci-dessus), un bindage statique au // chargement de la page ne les couvrirait pas. document.addEventListener('change', function (ev) { if (ev.target.classList.contains('personnageAnimName')) { _saveCustomAnimations(); } else if (ev.target.classList.contains('personnageFrameUpload')) { var input = ev.target; if (!input.files.length) return; var row = input.closest('.personnageAnimRow'); var framesWrap = row.querySelector('.personnageAnimFrames'); var status = row.querySelector('.uploadStatus'); status.textContent = 'Envoi en cours...'; var uploads = Array.prototype.map.call(input.files, function (file) { var data = new FormData(); data.append('file', file); return fetch('/game/' + GAME_SLUG + '/upload', { method: 'POST', body: data }).then(function (r) { return r.json(); }); }); Promise.all(uploads).then(function (results) { results.forEach(function (json) { if (!json.url) return; var img = document.createElement('img'); img.className = 'personnageFrameThumb'; img.alt = ''; img.style.cssText = 'width:32px; height:32px; object-fit:contain; background:var(--panel2); border-radius:4px;'; img.src = json.url; framesWrap.appendChild(img); }); status.textContent = results.every(function (j) { return j.url; }) ? 'Fichier(s) envoyé(s) ✓' : "Échec de l'envoi pour certains fichiers."; input.value = ''; _saveCustomAnimations(); }); } });