367 lines
17 KiB
HTML
367 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Crumbblocks – Wasserkocher (Demo)</title>
|
||
<!-- Feste Blockly-Version (verhindert textToDom-Fehler) -->
|
||
<script src="https://unpkg.com/blockly@9.3.3/blockly.min.js"></script>
|
||
<script src="https://unpkg.com/blockly@9.3.3/msg/de.js"></script>
|
||
<style>
|
||
:root {
|
||
--bg: #0b1016;
|
||
--panel: #0e141b;
|
||
--ink: #e6edf3;
|
||
--muted: #93a1b3;
|
||
--accent: #4caf50;
|
||
--border: #1f2a37;
|
||
--console: #0a0f14;
|
||
--console-ink: #b7ffb2;
|
||
}
|
||
html, body {
|
||
height: 100%;
|
||
margin: 0;
|
||
background: var(--bg);
|
||
color: var(--ink);
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||
}
|
||
.wrap {
|
||
display: grid;
|
||
grid-template-rows: auto auto 1fr auto;
|
||
gap: 12px;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 16px;
|
||
}
|
||
header h1 { font-size: 20px; margin: 0; }
|
||
.controls {
|
||
display: flex; gap: 8px; flex-wrap: wrap;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px; padding: 10px;
|
||
}
|
||
button {
|
||
background: var(--accent);
|
||
color: #fff;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
padding: 10px 14px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
}
|
||
button.secondary { background: #374151; }
|
||
button:active { transform: translateY(1px); }
|
||
.stage {
|
||
display: grid; grid-template-rows: 64vh 28vh; gap: 12px;
|
||
}
|
||
#blocklyDiv {
|
||
width: 100%;
|
||
height: 64vh;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
#output {
|
||
width: 100%;
|
||
height: 28vh;
|
||
background: var(--console);
|
||
color: var(--console-ink);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
margin: 0;
|
||
overflow: auto;
|
||
white-space: pre-wrap;
|
||
}
|
||
.hint {
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
margin-top: 6px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<header>
|
||
<h1>🧁 Crumbblocks – Virtueller Wasserkocher (Kids-Demo)</h1>
|
||
<div class="hint">
|
||
Variablen: <code>power_w</code>, <code>wasser_ml</code>, <code>sensor_temp_c</code>, <code>zeit_s</code>,
|
||
<code>energie_wh</code>, <code>verbrauch_wh</code>. Ziel: Bis 100 °C erhitzen, Energie/Zeit mitrechnen.
|
||
</div>
|
||
</header>
|
||
|
||
<div class="controls">
|
||
<button onclick="loadDemo()">🎬 Demo laden</button>
|
||
<button onclick="runCode()">▶️ Ausführen</button>
|
||
<button class="secondary" onclick="clearOutput()">🧹 Leeren</button>
|
||
<span class="hint">Sendet per <code>POST /crumbapi/blockly-terminal</code> (Fallback: Echo).</span>
|
||
</div>
|
||
|
||
<div class="stage">
|
||
<div id="blocklyDiv"></div>
|
||
<pre id="output">🌲 Terminal wartet …</pre>
|
||
</div>
|
||
|
||
<!-- TOOLBOX (Standardkategorien + Variablen/Funktionen) -->
|
||
<xml id="toolbox" style="display:none">
|
||
<category name="Logik" categorystyle="logic_category">
|
||
<block type="controls_if"></block>
|
||
<block type="logic_compare"></block>
|
||
<block type="logic_operation"></block>
|
||
<block type="logic_boolean"></block>
|
||
</category>
|
||
<category name="Schleifen" categorystyle="loop_category">
|
||
<block type="controls_repeat_ext"><value name="TIMES"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||
<block type="controls_whileUntil"><field name="MODE">WHILE</field></block>
|
||
<block type="controls_flow_statements"></block>
|
||
</category>
|
||
<category name="Mathe" categorystyle="math_category">
|
||
<block type="math_number"></block>
|
||
<block type="math_arithmetic"></block>
|
||
<block type="math_round"></block>
|
||
</category>
|
||
<category name="Text" categorystyle="text_category">
|
||
<block type="text"></block>
|
||
<block type="text_print"></block>
|
||
</category>
|
||
<sep></sep>
|
||
<category name="Variablen" categorystyle="variable_category" custom="VARIABLE_DYNAMIC"></category>
|
||
<category name="Funktionen" categorystyle="procedure_category" custom="PROCEDURE"></category>
|
||
</xml>
|
||
|
||
<!-- DEMO-WORKSPACE (Wasserkocher) -->
|
||
<script id="demo-xml" type="text/plain">
|
||
<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>
|
||
|
||
<!-- Startwerte -->
|
||
<block type="variables_set" x="24" y="24">
|
||
<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>
|
||
<!-- Sicherheitslimit Wasser -->
|
||
<block type="controls_if">
|
||
<value name="IF0">
|
||
<block type="logic_compare">
|
||
<field name="OP">GT</field>
|
||
<value name="A"><block type="variables_get"><field name="VAR">wasser_ml</field></block></value>
|
||
<value name="B"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||
</block>
|
||
</value>
|
||
<statement name="DO0">
|
||
<block type="variables_set">
|
||
<field name="VAR">wasser_ml</field>
|
||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||
</block>
|
||
</statement>
|
||
<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>
|
||
<!-- while (sensor_temp_c < 100) -->
|
||
<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">
|
||
<!-- Abbruch wenn kein Strom -->
|
||
<block type="controls_if">
|
||
<value name="IF0">
|
||
<block type="logic_compare">
|
||
<field name="OP">LTE</field>
|
||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||
<value name="B"><block type="math_number"><field name="NUM">0</field></block></value>
|
||
</block>
|
||
</value>
|
||
<statement name="DO0">
|
||
<block type="text_print">
|
||
<value name="TEXT"><block type="text"><field name="TEXT">Kein Strom → STOP</field></block></value>
|
||
<next>
|
||
<block type="controls_flow_statements"><field name="FLOW">BREAK</field></block>
|
||
</next>
|
||
</block>
|
||
</statement>
|
||
<next>
|
||
<!-- Heize-Schritt -->
|
||
<block type="text_print">
|
||
<value name="TEXT"><block type="text"><field name="TEXT">Heize…</field></block></value>
|
||
<next>
|
||
<!-- sensor_temp_c += 5 -->
|
||
<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>
|
||
<!-- zeit_s += 5 -->
|
||
<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>
|
||
<!-- energie_wh += power_w * 5 / 3600 -->
|
||
<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_arithmetic">
|
||
<field name="OP">DIVIDE</field>
|
||
<value name="A">
|
||
<block type="math_arithmetic">
|
||
<field name="OP">MULTIPLY</field>
|
||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||
</block>
|
||
</value>
|
||
<value name="B"><block type="math_number"><field name="NUM">3600</field></block></value>
|
||
</block>
|
||
</value>
|
||
</block>
|
||
</value>
|
||
<next>
|
||
<block type="variables_set">
|
||
<field name="VAR">verbrauch_wh</field>
|
||
<value name="VALUE"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||
</block>
|
||
</next>
|
||
</block>
|
||
</next>
|
||
</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>
|
||
</next>
|
||
</block>
|
||
</xml>
|
||
</script>
|
||
|
||
<script>
|
||
// Workspace
|
||
const workspace = Blockly.inject('blocklyDiv', {
|
||
toolbox: document.getElementById('toolbox'),
|
||
theme: Blockly.Themes.Dark,
|
||
renderer: 'geras',
|
||
grid: { spacing: 24, length: 3, colour: '#223045', snap: true },
|
||
zoom: { controls: true, wheel: true, startScale: 1.05, maxScale: 2.0, minScale: 0.5, pinch: true },
|
||
trashcan: true
|
||
});
|
||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||
|
||
function out(txt, replace=false) {
|
||
const el = document.getElementById('output');
|
||
el.textContent = replace ? String(txt) : (el.textContent + '\n' + String(txt));
|
||
el.scrollTop = el.scrollHeight;
|
||
}
|
||
function clearOutput() { document.getElementById('output').textContent = '🌲 Terminal wartet …'; }
|
||
|
||
function loadDemo() {
|
||
try {
|
||
const xmlTxt = document.getElementById('demo-xml').textContent.trim();
|
||
const dom = (Blockly.Xml && Blockly.Xml.textToDom)
|
||
? Blockly.Xml.textToDom(xmlTxt)
|
||
: Blockly.utils.xml.textToDom(xmlTxt);
|
||
workspace.clear();
|
||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||
out('✅ Demo geladen.', true);
|
||
} catch (e) {
|
||
out('❌ Demo konnte nicht geladen werden: ' + e, true);
|
||
}
|
||
}
|
||
|
||
async function runCode() {
|
||
const code = Blockly.JavaScript.workspaceToCode(workspace);
|
||
out('📤 Sende an Terminal…\n\n' + code, true);
|
||
|
||
// Variablenliste (für spätere Auswertung im Backend hilfreich)
|
||
const knownVars = ['sensor_temp_c','power_w','wasser_ml','zeit_s','energie_wh','verbrauch_wh'];
|
||
|
||
try {
|
||
const res = await fetch('/crumbapi/blockly-terminal', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ mode: 'blockly', blockcode: code, vars: knownVars })
|
||
});
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
const data = await res.json();
|
||
out('\n✅ Antwort:\n' + JSON.stringify(data, null, 2));
|
||
} catch (err) {
|
||
// Fallback: Echo-JSON lokal anzeigen
|
||
const echo = {
|
||
ok: true,
|
||
mode: 'echo-fallback',
|
||
request: { mode: 'blockly', blockcode: code, vars: knownVars }
|
||
};
|
||
out('\nℹ️ Endpoint nicht bereit. Fallback aktiv.\n\n' + JSON.stringify(echo, null, 2));
|
||
}
|
||
}
|
||
|
||
// Direkt beim Laden einmal die Demo hineinlegen:
|
||
loadDemo();
|
||
</script>
|
||
</div>
|
||
</body>
|
||
</html>
|