| <!DOCTYPE html>
|
| <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;
|
| }
|
| .asset-row input, .asset-row select,
|
| .sound-row input, .sound-row select {
|
| margin-right: 0;
|
| }
|
| #backdrops-container .asset-row,
|
| #sprites-container .asset-row {
|
| flex-direction: column;
|
| align-items: flex-start;
|
| border: 1px solid #ddd;
|
| padding: 1em;
|
| margin-bottom: 1em;
|
| border-radius: 5px;
|
| }
|
| .asset-main-row {
|
| display: flex;
|
| align-items: center;
|
| width: 100%;
|
| gap: 0.5em;
|
| }
|
| .asset-sounds-container {
|
| 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 = [];
|
|
|
|
|
| async function loadAssetLists() {
|
| const resp = await fetch('/list_assets');
|
| const { backdrops, sprites, sounds } = await resp.json();
|
| availableBackdrops = backdrops;
|
| availableSprites = sprites;
|
| availableSounds = sounds;
|
| }
|
|
|
|
|
| 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`);
|
|
|
| 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;
|
|
|
|
|
| row.querySelector('.add-sound').onclick = () => {
|
| makeSoundRow(row.querySelector('.asset-individual-sounds'), availableSounds, `${type} Sound`);
|
| };
|
|
|
| row.querySelector('.remove').onclick = () => row.remove();
|
| container.append(row);
|
| }
|
|
|
|
|
| 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');
|
| };
|
|
|
|
|
|
|
|
|
| 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 = {};
|
|
|
| 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) {
|
|
|
| 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');
|
|
|
|
|
|
|
|
|
|
|
| const payload = {
|
| description: desc,
|
| backdrops: backdropsData.assets,
|
| sprites: spritesData.assets,
|
|
|
| backdrop_sounds: backdropsData.sounds,
|
| 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);
|
| });
|
|
|
|
|
| loadAssetLists();
|
| </script>
|
| </body>
|
| </html> |