299 lines
13 KiB
HTML
299 lines
13 KiB
HTML
<!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>
|