|
<!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> |