import { SpriteEditor } from './SpriteEditor.js'; import { SpriteSheet } from '../core/SpriteSheet.js'; /** * Manages the sprite editor modal integration. */ export class SpriteEditorManager { /** * @param {HTMLElement} modal Modal element. * @param {HTMLElement} container Container for sprite editor. */ constructor(modal, container) { this.modal = modal; this.container = container; this.spriteSheet = new SpriteSheet(); this.editor = null; this.isOpen = false; this.#setupModal(); } /** * Sets up modal event listeners. */ #setupModal() { // Close modal on backdrop click const backdrop = this.modal.querySelector('.modal__backdrop'); backdrop?.addEventListener('click', () => this.close()); // Close modal on close button click const closeButtons = this.modal.querySelectorAll('[data-modal-close]'); closeButtons.forEach((btn) => { btn.addEventListener('click', () => this.close()); }); // Export to .p8 const exportBtn = document.getElementById('exportP8Button'); exportBtn?.addEventListener('click', () => this.#exportToP8()); // Import from .p8 const importBtn = document.getElementById('importP8Button'); importBtn?.addEventListener('click', () => this.#importFromP8()); // Initialize editor on first open this.modal.addEventListener('transitionend', () => { if (this.isOpen && !this.editor) { this.#initializeEditor(); } }, { once: true }); } /** * Initializes the sprite editor. */ #initializeEditor() { if (this.editor) return; this.editor = new SpriteEditor(this.container, this.spriteSheet); console.log('Sprite editor initialized'); } /** * Opens the sprite editor modal. */ open() { if (this.isOpen) return; this.isOpen = true; this.modal.removeAttribute('hidden'); this.modal.setAttribute('aria-hidden', 'false'); document.body.classList.add('modal-open'); // Initialize editor immediately if not already done if (!this.editor) { this.#initializeEditor(); } // Focus trap requestAnimationFrame(() => { const closeBtn = this.modal.querySelector('[data-modal-close]'); closeBtn?.focus(); }); } /** * Closes the sprite editor modal. */ close() { if (!this.isOpen) return; this.isOpen = false; this.modal.setAttribute('hidden', ''); this.modal.setAttribute('aria-hidden', 'true'); document.body.classList.remove('modal-open'); } /** * Exports sprite data to .p8 format. */ #exportToP8() { if (!this.editor) return; const gfxData = this.editor.getSpriteSheet().toP8Gfx(); const flagData = this.editor.getSpriteSheet().toP8Flags(); const p8Content = `pico-8 cartridge // http://www.pico-8.com version 41 __lua__ -- generated by picoGraph -- paste your blueprinted code here __gfx__ ${gfxData} __gff__ ${flagData} `; const blob = new Blob([p8Content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'sprites.p8'; a.click(); URL.revokeObjectURL(url); // Show success feedback const exportBtn = document.getElementById('exportP8Button'); if (exportBtn) { exportBtn.classList.add('is-success'); setTimeout(() => { exportBtn.classList.remove('is-success'); }, 1500); } } /** * Imports sprite data from .p8 format. */ #importFromP8() { if (!this.editor) return; const input = document.createElement('input'); input.type = 'file'; input.accept = '.p8'; input.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; try { const text = await file.text(); // Extract __gfx__ section const gfxMatch = text.match(/__gfx__\s*\n([\s\S]*?)(?=\n__|$)/); if (gfxMatch) { this.editor.getSpriteSheet().fromP8Gfx(gfxMatch[1]); } // Extract __gff__ section const gffMatch = text.match(/__gff__\s*\n([\s\S]*?)(?=\n__|$)/); if (gffMatch) { this.editor.getSpriteSheet().fromP8Flags(gffMatch[1]); } // Re-render this.editor.setSpriteSheet(this.editor.getSpriteSheet()); // Show success feedback const importBtn = document.getElementById('importP8Button'); if (importBtn) { importBtn.classList.add('is-success'); setTimeout(() => { importBtn.classList.remove('is-success'); }, 1500); } } catch (error) { console.error('Failed to import sprite data:', error); alert('Failed to import sprite data. Please ensure the file is a valid .p8 file.'); } }); input.click(); } /** * Gets the current sprite sheet data. * * @returns {SpriteSheet} */ getSpriteSheet() { return this.spriteSheet; } /** * Sets sprite sheet data. * * @param {SpriteSheet} spriteSheet New sprite sheet. */ setSpriteSheet(spriteSheet) { this.spriteSheet = spriteSheet; if (this.editor) { this.editor.setSpriteSheet(spriteSheet); } } }