Files
crumbmissions/crumbblocks/index.html
2025-12-23 20:52:00 +01:00

299 lines
13 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Blockly to Crumbforest Terminal Bridge</title>
<!-- Blockly + JS Generator + Deutsche Labels -->
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<style>
:root {
--bg: #1e1e1e;
--panel: #111;
--text: #e8e8e8;
--ok: #a6e22e;
--btn: #4caf50;
}
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: monospace; }
header { display:flex; gap:.5rem; align-items:center; padding:.5rem .75rem; }
header h2 { margin:0; font-weight:600; }
header .spacer { flex:1; }
button {
padding:.6rem .9rem; background: var(--btn); color:#fff; border:none; border-radius:8px; font-weight:700; cursor:pointer;
}
#wrap { display:flex; flex-direction:column; height: calc(100vh - 56px); }
#blocklyDiv { height: 64vh; width: 100%; }
#bottom { display:flex; gap:.75rem; padding:.5rem .75rem; }
#output {
flex:1; height: 32vh; background:var(--panel); color:var(--ok);
padding:10px; overflow:auto; white-space: pre-wrap; word-break: break-word; border-radius:8px;
}
#controls { display:flex; flex-direction:column; gap:.5rem; }
.muted { opacity:.7; }
</style>
</head>
<body>
<header>
<h2>🧁 Blockly Crumbforest Bridge</h2>
<div class="spacer"></div>
<button id="btnDemo" title="Mini-Demo laden">🧪 Demo</button>
<button id="btnClear" title="Workspace leeren">🗑️ Neu</button>
<button id="btnZoomIn" title="Zoom +">🔍+</button>
<button id="btnZoomOut" title="Zoom ">🔍-</button>
<button id="btnRun" title="Code ausführen / an Terminal senden">▶️ Ausführen</button>
</header>
<div id="wrap">
<div id="blocklyDiv"></div>
<div id="bottom">
<div id="controls">
<div class="muted">Variablen: energie_wh, verbrauch_wh, sensor_temp_c, power_w, wasser_ml, zeit_s</div>
<div class="muted">Endpoint: <code>/crumbapi/blockly-terminal</code> (Echo-Fallback aktiv)</div>
</div>
<pre id="output">🌲 Terminal wartet ...</pre>
</div>
</div>
<!-- Toolbox: wie bei dir, nur Loops aktiviert -->
<xml id="toolbox" style="display:none">
<block type="text"></block>
<block type="text_print"></block>
<block type="controls_if"></block>
<block type="logic_compare"></block>
<block type="math_number"></block>
<block type="math_arithmetic"></block>
<block type="variables_set"></block>
<block type="variables_get"></block>
<block type="controls_repeat_ext"></block>
<block type="controls_whileUntil"></block>
<block type="procedures_defnoreturn"></block>
<block type="procedures_callnoreturn"></block>
</xml>
<script>
const workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'),
theme: Blockly.Themes.Dark,
renderer: 'zelos',
grid: {spacing: 24, length: 3, colour: '#484848', snap: true},
trashcan: true,
zoom: {startScale: 1.15, maxScale: 2.0, minScale: 0.6, controls: false, wheel: true},
move: {scrollbars: true, drag: true, wheel: true}
});
// Vordefinierte Variablen für euren „Prozess“-Wortschatz
['energie_wh','verbrauch_wh','sensor_temp_c','power_w','wasser_ml','zeit_s']
.forEach(v => { try { workspace.createVariable(v); } catch(e){} });
// Helpers
const $ = sel => document.querySelector(sel);
function setOutput(txt){ $('#output').textContent = txt; }
function appendOutput(txt){
const el = $('#output');
el.textContent += (el.textContent.endsWith('\n')?'':'\n') + txt;
el.scrollTop = el.scrollHeight;
}
// Initial-Variablen aus „set“-Blöcken sammeln (für JSON-Vorschau)
function collectInitialVars() {
const vals = {};
const blocks = workspace.getAllBlocks(false);
for (const b of blocks) {
if (b.type === 'variables_set') {
const name = b.getFieldValue('VAR');
// Versuche simples Literal zu lesen (Zahl/Text)
const input = b.getInputTargetBlock('VALUE');
if (input) {
if (input.type === 'math_number') {
vals[name] = Number(input.getFieldValue('NUM'));
} else if (input.type === 'text') {
vals[name] = input.getFieldValue('TEXT') ?? '';
} else {
// Irgendwas anderes verschachtelt → nur markieren
vals[name] = '<expr>';
}
} else {
vals[name] = '<unset>';
}
}
}
return vals;
}
// Kompatibles XML-Parserchen (fix für „Blockly.Xml.textToDom is not a function“)
function textToDom(xmlText){
if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText);
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
}
// Aktionen
$('#btnZoomIn').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(1);
$('#btnZoomOut').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(-1);
$('#btnClear').onclick = () => {
workspace.clear();
setOutput('🧹 Workspace geleert.');
};
$('#btnDemo').onclick = () => {
const demoXML = `
<xml xmlns="https://developers.google.com/blockly/xml">
<variables>
<variable>sensor_temp_c</variable>
<variable>power_w</variable>
<variable>wasser_ml</variable>
<variable>zeit_s</variable>
<variable>energie_wh</variable>
<variable>verbrauch_wh</variable>
</variables>
<block type="variables_set" x="24" y="22">
<field name="VAR">sensor_temp_c</field>
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
<next>
<block type="variables_set">
<field name="VAR">power_w</field>
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
<next>
<block type="variables_set">
<field name="VAR">wasser_ml</field>
<value name="VALUE"><block type="math_number"><field name="NUM">250</field></block></value>
<next>
<block type="variables_set">
<field name="VAR">zeit_s</field>
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
<next>
<block type="variables_set">
<field name="VAR">energie_wh</field>
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
<next>
<block type="variables_set">
<field name="VAR">verbrauch_wh</field>
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
<next>
<block type="controls_whileUntil">
<field name="MODE">WHILE</field>
<value name="BOOL">
<block type="logic_compare">
<field name="OP">LT</field>
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
</block>
</value>
<statement name="DO">
<block type="text_print">
<value name="TEXT">
<block type="text">
<field name="TEXT">Heize...</field>
</block>
</value>
<next>
<block type="variables_set">
<field name="VAR">sensor_temp_c</field>
<value name="VALUE">
<block type="math_arithmetic">
<field name="OP">ADD</field>
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
</block>
</value>
<next>
<block type="variables_set">
<field name="VAR">zeit_s</field>
<value name="VALUE">
<block type="math_arithmetic">
<field name="OP">ADD</field>
<value name="A"><block type="variables_get"><field name="VAR">zeit_s</field></block></value>
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
</block>
</value>
<next>
<block type="variables_set">
<field name="VAR">energie_wh</field>
<value name="VALUE">
<block type="math_arithmetic">
<field name="OP">ADD</field>
<value name="A"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
<value name="B"><block type="math_number"><field name="NUM">2.8</field></block></value>
</block>
</value>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</statement>
<next>
<block type="text_print">
<value name="TEXT">
<block type="text">
<field name="TEXT">Fertig. STOP.</field>
</block>
</value>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</next>
</block>
</xml>`;
try {
const dom = textToDom(demoXML);
workspace.clear();
Blockly.Xml.domToWorkspace(dom, workspace);
setOutput('✅ Demo geladen. Passe die Logik an und drücke ▶️ Ausführen.');
} catch (e) {
setOutput('❌ Demo konnte nicht geladen werden:\n' + e);
}
};
$('#btnRun').onclick = async () => {
const code = Blockly.JavaScript.workspaceToCode(workspace);
const initVars = collectInitialVars();
setOutput("📤 Sende an Terminal...\n\n" + code);
const payload = {
mode: "blockly",
blockcode: code,
preview_vars: initVars
};
try {
const res = await fetch("/crumbapi/blockly-terminal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
let data;
if (res.ok) {
data = await res.json().catch(()=>({}));
appendOutput("\n\n✅ Antwort:\n" + JSON.stringify(data, null, 2));
} else {
appendOutput("\n\n Endpoint nicht bereit (HTTP " + res.status + "). Fallback aktiv.");
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
}
} catch (err) {
appendOutput("\n\n❌ Netzwerkfehler: " + err);
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
}
};
// Resizing
window.addEventListener('resize', () => Blockly.svgResize(workspace));
</script>
</body>
</html>