Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Scratch Game JSON Generator</title> | |
| <style> | |
| body { font-family: Arial, sans-serif; margin: 2rem; background: #f9f9f9; } | |
| .asset-row, .sound-row { | |
| margin-bottom: .5em; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5em; /* Add some space between elements */ | |
| } | |
| .asset-row input, .asset-row select, | |
| .sound-row input, .sound-row select { | |
| margin-right: 0; /* Reset margin-right for better gap control */ | |
| } | |
| #backdrops-container .asset-row, | |
| #sprites-container .asset-row { | |
| flex-direction: column; /* Stack name/file and its sounds */ | |
| align-items: flex-start; | |
| border: 1px solid #ddd; | |
| padding: 1em; | |
| margin-bottom: 1em; | |
| border-radius: 5px; | |
| } | |
| .asset-main-row { /* New class for the main part of asset row */ | |
| display: flex; | |
| align-items: center; | |
| width: 100%; | |
| gap: 0.5em; | |
| } | |
| .asset-sounds-container { /* Reusable for both backdrops and sprites */ | |
| margin-top: 0.5em; | |
| padding-left: 1em; | |
| border-left: 2px solid #eee; | |
| } | |
| button { margin-top: 1rem; padding: .5rem 1rem; font-size: 1rem; cursor: pointer; } | |
| button.remove { | |
| background: #f44336; | |
| color: white; | |
| border: none; | |
| border-radius: 3px; | |
| padding: 0.3em 0.6em; | |
| font-size: 0.8em; | |
| cursor: pointer; | |
| margin-left: 0.5em; | |
| } | |
| button.add-sound { | |
| background: #4CAF50; | |
| color: white; | |
| border: none; | |
| border-radius: 3px; | |
| padding: 0.3em 0.6em; | |
| font-size: 0.8em; | |
| cursor: pointer; | |
| margin-top: 0.5em; | |
| } | |
| pre { background: #222; color: #eee; padding: 1rem; overflow-x: auto; max-height: 60vh; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Scratch Game JSON Generator</h1> | |
| <label for="gameDesc">Enter game description:</label><br /> | |
| <textarea id="gameDesc" rows="4" placeholder="e.g. jumping cat game over obstacle"></textarea><br /> | |
| <h2>Backdrops</h2> | |
| <div id="backdrops-container"></div> | |
| <button id="add-backdrop">+ Add Backdrop</button> | |
| <h2>Sprites</h2> | |
| <div id="sprites-container"></div> | |
| <button id="add-sprite">+ Add Sprite</button><br/> | |
| <button id="generateBtn">Generate Scratch JSON</button> | |
| <h2>Output JSON:</h2> | |
| <pre id="output">Waiting for input...</pre> | |
| <script> | |
| let availableBackdrops = []; | |
| let availableSprites = []; | |
| let availableSounds = []; // New array for available sounds | |
| // Fetch lists of files from server | |
| async function loadAssetLists() { | |
| const resp = await fetch('/list_assets'); | |
| const { backdrops, sprites, sounds } = await resp.json(); | |
| availableBackdrops = backdrops; | |
| availableSprites = sprites; | |
| availableSounds = sounds; | |
| } | |
| // Helper to create asset (backdrop/sprite) rows | |
| function makeAssetRow(containerId, available, type) { | |
| const container = document.getElementById(containerId); | |
| const row = document.createElement('div'); | |
| row.className = 'asset-row'; | |
| row.classList.add(`${type.toLowerCase()}-item`); // Add type-specific class | |
| let innerHTML = ` | |
| <div class="asset-main-row"> | |
| <input type="text" placeholder="${type} name" class="asset-name"/> | |
| <select class="asset-select"> | |
| ${available.map(a => `<option value="${a}">${a}</option>`).join('')} | |
| </select> | |
| <button class="remove">×</button> | |
| </div> | |
| <div class="asset-sounds-container"> | |
| <h3>Sounds for this ${type}:</h3> | |
| <div class="asset-individual-sounds"></div> | |
| <button type="button" class="add-sound">+ Add Sound</button> | |
| </div> | |
| `; | |
| row.innerHTML = innerHTML; | |
| // Attach event listener for adding sounds to this specific asset row | |
| row.querySelector('.add-sound').onclick = () => { | |
| makeSoundRow(row.querySelector('.asset-individual-sounds'), availableSounds, `${type} Sound`); | |
| }; | |
| row.querySelector('.remove').onclick = () => row.remove(); | |
| container.append(row); | |
| } | |
| // Helper to create sound rows | |
| function makeSoundRow(container, available, type) { | |
| const row = document.createElement('div'); | |
| row.className = 'sound-row'; | |
| row.innerHTML = ` | |
| <input type="text" placeholder="${type} name" class="sound-name"/> | |
| <select class="sound-select"> | |
| ${available.map(s => `<option value="${s}">${s}</option>`).join('')} | |
| </select> | |
| <button class="remove">×</button> | |
| `; | |
| row.querySelector('.remove').onclick = () => row.remove(); | |
| container.append(row); | |
| } | |
| document.getElementById('add-backdrop').onclick = () => { | |
| makeAssetRow('backdrops-container', availableBackdrops, 'Backdrop'); | |
| }; | |
| // REMOVED: add-stage-sound button listener | |
| // document.getElementById('add-stage-sound').onclick = () => { | |
| // makeSoundRow('stage-sounds-container', availableSounds, 'Stage Sound'); | |
| // }; | |
| document.getElementById('add-sprite').onclick = () => { | |
| makeAssetRow('sprites-container', availableSprites, 'Sprite'); | |
| }; | |
| document.getElementById('generateBtn').addEventListener('click', async () => { | |
| const desc = document.getElementById('gameDesc').value.trim(); | |
| if (!desc) return alert('Please enter a game description.'); | |
| const collectAssetAndSounds = (containerId, type) => { | |
| const collectedData = []; | |
| const soundsPayload = {}; // Keyed by asset name (backdrop or sprite) | |
| Array.from(document.getElementById(containerId).querySelectorAll(`.${type.toLowerCase()}-item`)).forEach(itemRow => { | |
| const itemNameInput = itemRow.querySelector('.asset-main-row .asset-name'); | |
| const itemFilenameSelect = itemRow.querySelector('.asset-main-row .asset-select'); | |
| const itemName = itemNameInput.value.trim(); | |
| const itemFilename = itemFilenameSelect.value; | |
| if (itemName && itemFilename) { | |
| collectedData.push({ | |
| name: itemName, | |
| filename: itemFilename | |
| }); | |
| const individualSoundsContainer = itemRow.querySelector('.asset-individual-sounds'); | |
| if (individualSoundsContainer) { | |
| // Associate sounds with the asset's name | |
| soundsPayload[itemName] = Array.from(individualSoundsContainer.querySelectorAll('.sound-row')) | |
| .map(row => ({ | |
| name: row.querySelector('.sound-name').value.trim(), | |
| filename: row.querySelector('.sound-select').value | |
| })) | |
| .filter(s => s.name && s.filename); | |
| } | |
| } | |
| }); | |
| return { assets: collectedData, sounds: soundsPayload }; | |
| }; | |
| const backdropsData = collectAssetAndSounds('backdrops-container', 'Backdrop'); | |
| const spritesData = collectAssetAndSounds('sprites-container', 'Sprite'); | |
| // The stage is special. If you want a global "stage" sound that isn't tied to a specific backdrop, | |
| // you'd need a separate input for it. For now, we're assuming sounds are tied to backdrops. | |
| // If there's only one backdrop, its sounds effectively become "stage sounds". | |
| const payload = { | |
| description: desc, | |
| backdrops: backdropsData.assets, | |
| sprites: spritesData.assets, | |
| // Pass backdrop sounds and sprite sounds separately for the backend | |
| backdrop_sounds: backdropsData.sounds, // New: Sounds keyed by backdrop name | |
| sprite_sounds: spritesData.sounds | |
| }; | |
| const output = document.getElementById('output'); | |
| output.textContent = 'Generating...'; | |
| const resp = await fetch('/generate_game', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!resp.ok) { | |
| const err = await resp.json(); | |
| output.textContent = 'Error: ' + (err.error || resp.statusText); | |
| return; | |
| } | |
| const data = await resp.json(); | |
| output.textContent = JSON.stringify(data, null, 2); | |
| }); | |
| // initial load | |
| loadAssetLists(); | |
| </script> | |
| </body> | |
| </html> |