Blockly review

This commit is contained in:
Michael Schimmel
2025-06-11 00:17:58 +02:00
parent 7516bd3d9d
commit b8a530254d
6 changed files with 644 additions and 241 deletions
+53 -44
View File
@@ -1,7 +1,7 @@
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'Form1'
Caption = 'Blockly LLVM IR Generator'
ClientHeight = 872
ClientWidth = 1010
FormFactor.Width = 320
@@ -12,57 +12,66 @@ object MainForm: TMainForm
DesignerMasterStyle = 0
object WebBrowser1: TWebBrowser
Align = Top
Anchors = [akLeft, akTop, akRight, akBottom]
Anchors = [akLeft, akTop]
Size.Width = 1010.000000000000000000
Size.Height = 633.000000000000000000
Size.Height = 625.000000000000000000
Size.PlatformDefault = False
WindowsEngine = EdgeOnly
OnDidFinishLoad = WebBrowser1DidFinishLoad
end
object LauchSiteButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 744.000000000000000000
TabOrder = 1
Text = 'Lauch Site'
TextSettings.Trimming = None
OnClick = LauchSiteButtonClick
end
object SaveWorkspaceButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 774.000000000000000000
Size.Width = 97.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
Text = 'Save Workspace'
TextSettings.Trimming = None
OnClick = SaveWorkspaceButtonClick
end
object GenerateCodeButton: TButton
Anchors = [akLeft, akBottom]
Position.X = 24.000000000000000000
Position.Y = 804.000000000000000000
Size.Width = 97.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
Text = 'Generate Code'
TextSettings.Trimming = None
OnClick = GenerateCodeButtonClick
object ToolBar1: TToolBar
Size.Width = 1010.000000000000000000
Size.Height = 40.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
object RefreshButton: TSpeedButton
Align = Left
Size.Width = 80.000000000000000000
Size.Height = 40.000000000000000000
Size.PlatformDefault = False
Text = 'Refresh'
TextSettings.Trimming = None
OnClick = LauchSiteButtonClick
end
object SaveWorkspaceButton: TSpeedButton
Align = Left
Position.X = 80.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 40.000000000000000000
Size.PlatformDefault = False
Text = 'Save'
TextSettings.Trimming = None
OnClick = SaveWorkspaceButtonClick
end
object GenerateCodeButton: TSpeedButton
Align = Left
Position.X = 160.000000000000000000
Size.Width = 80.000000000000000000
Size.Height = 40.000000000000000000
Size.PlatformDefault = False
Text = 'Generate'
TextSettings.Trimming = None
OnClick = GenerateCodeButtonClick
end
end
end
object Memo: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
Anchors = [akRight, akBottom]
Position.X = 144.000000000000000000
Position.Y = 641.000000000000000000
Size.Width = 841.000000000000000000
Size.Height = 223.000000000000000000
Align = Client
Size.Width = 1010.000000000000000000
Size.Height = 239.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
Viewport.Width = 1006.000000000000000000
Viewport.Height = 235.000000000000000000
end
object Splitter1: TSplitter
Align = Top
Cursor = crVSplit
MinSize = 20.000000000000000000
Position.Y = 625.000000000000000000
Size.Width = 1010.000000000000000000
Size.Height = 8.000000000000000000
Size.PlatformDefault = False
TabOrder = 4
Viewport.Width = 837.000000000000000000
Viewport.Height = 219.000000000000000000
end
end
+8 -5
View File
@@ -11,7 +11,7 @@ uses
const
// Pfad zur HTML-Datei
PATH_BLOCKLY = 'T:\Myc\Blockly\blockly_LLVM.html';
PATH_BLOCKLY = 'T:\Myc\Blockly\index.html';
// Pfade zu den LLVM-Werkzeugen
CLANG_PATH = 'clang.exe';
@@ -20,10 +20,12 @@ const
type
TMainForm = class( TForm )
WebBrowser1: TWebBrowser;
LauchSiteButton: TButton;
SaveWorkspaceButton: TButton;
GenerateCodeButton: TButton;
Memo: TMemo;
Splitter1: TSplitter;
ToolBar1: TToolBar;
RefreshButton: TSpeedButton;
SaveWorkspaceButton: TSpeedButton;
GenerateCodeButton: TSpeedButton;
procedure FormCreate( Sender: TObject );
procedure FormDestroy( Sender: TObject );
procedure LauchSiteButtonClick( Sender: TObject );
@@ -83,6 +85,7 @@ end;
procedure TMainForm.FormCreate( Sender: TObject );
begin
TLLVMRunner.Log := Memo.Lines;
LauchSiteButtonClick(Self);
end;
procedure TMainForm.FormDestroy( Sender: TObject );
@@ -104,7 +107,7 @@ var
cmdLine: string;
buffer: TBytes;
bytesRead: Cardinal;
output, errors: string;
output: string;
begin
// Pipe für StdOut und StdErr erstellen
sa.nLength := SizeOf( TSecurityAttributes );
-192
View File
@@ -1,192 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly zu LLVM IR - Final</title>
<style>
body { font-family: sans-serif; }
#container { display: flex; }
#outputArea { margin-left: 20px; }
#codeOutput { width: 500px; height: 580px; font-family: 'Courier New', Courier, monospace; font-size: 14px; white-space: pre-wrap; border: 1px solid #ccc; }
#blocklyDiv { height: 600px; width: 800px; }
.button-bar { margin-bottom: 10px; }
</style>
</head>
<body>
<h1>LLVM IR (.dll) Generator</h1>
<p>Bauen Sie links Ihre Logik. Rechts erscheint der LLVM-Code, der zu einer 64-Bit DLL für Windows kompiliert werden kann.</p>
<div class="button-bar">
<button onclick="saveWorkspace()">Speichern</button>
<button onclick="loadWorkspace()">Letzten Stand laden</button>
<button onclick="generateAndPostCode()">Code generieren und posten</button>
</div>
<div id="container">
<div id="blocklyDiv"></div>
<div id="outputArea">
<h2>Erzeugter LLVM-Code:</h2>
<textarea id="codeOutput" readonly></textarea>
</div>
</div>
<script src="https://unpkg.com/blockly/blockly_compressed.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<script>
window.onload = function() {
let workspace;
// === LLVM-IR-GENERATOR (FINALE VERSION) ===
Blockly.LLVM = new Blockly.Generator('LLVM');
Blockly.LLVM.ORDER_ATOMIC = 0;
Blockly.LLVM.ORDER_NONE = 99;
// --- Block-Definitionen für die Toolbox ---
Blockly.defineBlocksWithJsonArray([
{
"type": "host_log_integer",
"message0": "Logge Integer %1",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": 290,
"tooltip": "Sendet einen Integer-Wert an den Delphi-Host.",
"helpUrl": ""
}
]);
// --- Generator-Implementierungen ---
Blockly.LLVM.regCounter = 0;
Blockly.LLVM.newReg = function() {
return '%' + (Blockly.LLVM.regCounter++);
};
Blockly.LLVM.workspaceToCode = function(workspace) {
Blockly.LLVM.regCounter = 1;
const allVariables = workspace.getAllVariables();
let varAllocations = '';
if (allVariables.length > 0) {
allVariables.forEach(v => {
const safeVarName = v.name.replace(/ /g, '_');
varAllocations += ` @${safeVarName} = common global i32 0\n`;
});
}
const topBlock = workspace.getTopBlocks(true)[0];
const code = topBlock ? Blockly.LLVM.blockToCode(topBlock) : '';
let moduleCode = `target triple = "x86_64-pc-win32"\n\n`;
moduleCode += `@gLogIntProc = common global void (i32)* null\n\n`;
if (varAllocations) {
moduleCode += '; Global Variables\n';
moduleCode += varAllocations + '\n';
}
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc) {\n';
moduleCode += 'entry:\n';
moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n\n';
moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n');
moduleCode += '\n ret void\n';
moduleCode += '}\n';
return moduleCode;
};
Blockly.LLVM.scrub_ = function(block, code) {
const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
const nextCode = nextBlock ? Blockly.LLVM.blockToCode(nextBlock) : '';
return code + nextCode;
};
Blockly.LLVM.forBlock['variables_set'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const value = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '0';
return `store i32 ${value}, i32* @${varName}\n`;
};
Blockly.LLVM.forBlock['math_change'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const delta = Blockly.LLVM.valueToCode(block, 'DELTA', Blockly.LLVM.ORDER_ATOMIC) || '0';
const loadReg = Blockly.LLVM.newReg();
const addReg = Blockly.LLVM.newReg();
let code = '';
code += `${loadReg} = load i32, i32* @${varName}\n`;
code += `${addReg} = add nsw i32 ${loadReg}, ${delta}\n`;
code += `store i32 ${addReg}, i32* @${varName}\n`;
return code;
};
Blockly.LLVM.forBlock['variables_get'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
return ['@' + varName, Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['math_number'] = function(block) {
return [String(block.getFieldValue('NUM')), Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['host_log_integer'] = function(block) {
const varName = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '@unknown';
const valueReg = Blockly.LLVM.newReg();
const logPtrReg = Blockly.LLVM.newReg();
let code = '';
code += `${valueReg} = load i32, i32* ${varName}\n`;
code += `${logPtrReg} = load void (i32)*, void (i32)** @gLogIntProc\n`;
code += `call void ${logPtrReg}(i32 ${valueReg})\n`;
return code;
};
// === INITIALISIERUNG VON BLOCKLY ===
const toolboxXml = `
<xml>
<category name="Variablen" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
<category name="Mathematik" colour="%{BKY_MATH_HUE}">
<block type="math_number"></block>
<block type="math_change">
<value name="DELTA"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
</block>
</category>
<category name="Host" colour="290">
<block type="host_log_integer"></block>
</category>
</xml>`;
workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml
});
function generateAndPostCode() {
const code = Blockly.LLVM.workspaceToCode(workspace);
if (window.chrome && window.chrome.webview) {
window.chrome.webview.postMessage(code);
} else {
console.log("PostMessage an Host nicht verfügbar. Code in Konsole ausgegeben:");
console.log(code);
}
}
window.generateAndPostCode = generateAndPostCode;
window.saveWorkspace = function() { const state = Blockly.serialization.workspaces.save(workspace); localStorage.setItem('blocklyWorkspace', JSON.stringify(state)); alert('Arbeitsbereich gespeichert!'); }
window.loadWorkspace = function() { const stateString = localStorage.getItem('blocklyWorkspace'); if (!stateString) { return; } const state = JSON.parse(stateString); Blockly.serialization.workspaces.load(state, workspace); }
function updateCode(event) { if (event.isUiEvent) return; const code = Blockly.LLVM.workspaceToCode(workspace); document.getElementById('codeOutput').value = code; }
workspace.addChangeListener(updateCode);
loadWorkspace();
};
</script>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly zu LLVM IR - Final</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>LLVM IR (.dll) Generator</h1>
<p>Bauen Sie links Ihre Logik. Rechts erscheint der LLVM-Code, der zu einer 64-Bit DLL für Windows kompiliert werden kann.</p>
<div class="button-bar">
<button onclick="saveWorkspace()">Speichern</button>
<button onclick="loadWorkspace()">Letzten Stand laden</button>
<button onclick="generateAndPostCode()">Code generieren und posten</button>
</div>
<div id="container">
<div id="blocklyDiv"></div>
<div id="outputContainer">
<div id="splitter"></div>
<div id="outputArea">
<h2>Erzeugter LLVM-Code:</h2>
<textarea id="codeOutput" readonly></textarea>
</div>
</div>
</div>
<script src="https://unpkg.com/blockly/blockly_compressed.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/de.js"></script>
<script src="script.js"></script>
</body>
</html>
+429
View File
@@ -0,0 +1,429 @@
window.onload = function() {
let workspace;
const isWebView = window.chrome && window.chrome.webview;
// === LLVM-IR-GENERATOR (FINALE VERSION) ===
Blockly.LLVM = new Blockly.Generator('LLVM');
Blockly.LLVM.ORDER_ATOMIC = 0;
Blockly.LLVM.ORDER_NONE = 99;
// --- Block-Definitionen für die Toolbox ---
Blockly.defineBlocksWithJsonArray([
{
"type": "host_log_integer",
"message0": "Logge Integer %1",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": 290,
"tooltip": "Sendet einen Integer-Wert an den Delphi-Host.",
"helpUrl": ""
},
{
"type": "current_datetime_utc",
"message0": "Aktueller UTC-Zeitstempel",
"output": "DateTimeUTC",
"colour": 120,
"tooltip": "Liefert den aktuellen Zeitstempel in UTC.",
"helpUrl": ""
},
{
"type": "get_datetime_component",
"message0": "Hole %1 von Zeitstempel %2 in Zeitzone %3",
"args0": [
{
"type": "field_dropdown",
"name": "COMPONENT",
"options": [
["Jahr", "YEAR"],
["Monat", "MONTH"],
["Tag", "DAY"],
["Stunde", "HOUR"],
["Minute", "MINUTE"],
["Sekunde", "SECOND"]
]
},
{
"type": "input_value",
"name": "DATETIME",
"check": "DateTimeUTC"
},
{
"type": "field_dropdown",
"name": "TIMEZONE",
"options": [
["UTC", "UTC"],
["ET (New York)", "ET"],
["WET (London)", "WET"],
["CET (Frankfurt)", "CET"],
["AEST (Sydney)", "AEST"],
["JST (Tokyo)", "JST"]
]
}
],
"output": "Number",
"colour": 120,
"tooltip": "Extrahiert eine Komponente (Jahr, Monat, etc.) aus einem UTC-Zeitstempel und konvertiert sie in die angegebene Zeitzone.",
"helpUrl": ""
}
]);
// --- Generator-Implementierungen ---
Blockly.LLVM.regCounter = 0;
Blockly.LLVM.newReg = function() {
return '%' + (Blockly.LLVM.regCounter++);
};
Blockly.LLVM.workspaceToCode = function(workspace) {
Blockly.LLVM.regCounter = 1;
Blockly.LLVM.generatedStrings = new Set();
Blockly.LLVM.globalDeclarations = '';
Blockly.LLVM.functionDeclarations = '';
const allVariables = workspace.getAllVariables();
let varAllocations = '';
if (allVariables.length > 0) {
allVariables.forEach(v => {
const safeVarName = v.name.replace(/ /g, '_');
varAllocations += ` @${safeVarName} = common global i32 0\n`;
});
}
const topBlock = workspace.getTopBlocks(true)[0];
const code = topBlock ? Blockly.LLVM.blockToCode(topBlock) : '';
let moduleCode = `target triple = "x86_64-pc-windows-msvc"\n\n`;
moduleCode += `; Definition des DateTimeUTC-Structs\n`;
moduleCode += `%struct.DateTimeUTC = type { i32, i32, i32, i32, i32, i32 }\n\n`;
if (Blockly.LLVM.globalDeclarations) {
moduleCode += '; Global string constants for timezones\n';
moduleCode += Blockly.LLVM.globalDeclarations + '\n';
}
moduleCode += `; Host Callback Declarations\n`;
moduleCode += `declare void @get_current_datetime_utc(%struct.DateTimeUTC*)\n`;
moduleCode += `declare i32 @get_datetime_component_in_zone(%struct.DateTimeUTC*, i32, i8*)\n`;
if (Blockly.LLVM.functionDeclarations) {
moduleCode += Blockly.LLVM.functionDeclarations + '\n';
}
moduleCode += `@gLogIntProc = common global void (i32)* null\n`;
moduleCode += `@gGetCurrentDateTimeUTCProc = common global void (%struct.DateTimeUTC*)* null\n`;
moduleCode += `@gGetComponentInZoneProc = common global i32 (%struct.DateTimeUTC*, i32, i8*)* null\n\n`;
if (varAllocations) {
moduleCode += '; Global Variables\n';
moduleCode += varAllocations + '\n';
}
moduleCode += 'define dllexport void @StarteLogik(void (i32)* %LogIntProc, void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc) {\n';
moduleCode += 'entry:\n';
moduleCode += ' store void (i32)* %LogIntProc, void (i32)** @gLogIntProc\n';
moduleCode += ' store void (%struct.DateTimeUTC*)* %GetCurrentDateTimeUTCProc, void (%struct.DateTimeUTC*)** @gGetCurrentDateTimeUTCProc\n';
moduleCode += ' store i32 (%struct.DateTimeUTC*, i32, i8*)* %GetComponentInZoneProc, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n\n';
moduleCode += code.split('\n').map(line => line ? ' ' + line : '').join('\n');
moduleCode += '\n ret void\n';
moduleCode += '}\n';
return moduleCode;
};
Blockly.LLVM.scrub_ = function(block, code) {
const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
const nextCode = nextBlock ? Blockly.LLVM.blockToCode(nextBlock) : '';
return code + nextCode;
};
Blockly.LLVM.forBlock['variables_set'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const value = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '0';
return `store i32 ${value}, i32* @${varName}\n`;
};
Blockly.LLVM.forBlock['math_change'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
const delta = Blockly.LLVM.valueToCode(block, 'DELTA', Blockly.LLVM.ORDER_ATOMIC) || '0';
const loadReg = Blockly.LLVM.newReg();
const addReg = Blockly.LLVM.newReg();
let code = '';
code += `${loadReg} = load i32, i32* @${varName}\n`;
code += `${addReg} = add nsw i32 ${loadReg}, ${delta}\n`;
code += `store i32 ${addReg}, i32* @${varName}\n`;
return code;
};
Blockly.LLVM.forBlock['variables_get'] = function(block) {
const varName = block.workspace.getVariableById(block.getFieldValue('VAR')).name.replace(/ /g, '_');
return ['@' + varName, Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['math_number'] = function(block) {
return [String(block.getFieldValue('NUM')), Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['host_log_integer'] = function(block) {
const valueCode = Blockly.LLVM.valueToCode(block, 'VALUE', Blockly.LLVM.ORDER_ATOMIC) || '0';
let valueReg;
let code = '';
if (valueCode.startsWith('@')) {
valueReg = Blockly.LLVM.newReg();
code += `${valueReg} = load i32, i32* ${valueCode}\n`;
} else if (valueCode.startsWith('%')) {
valueReg = valueCode;
} else {
valueReg = valueCode;
}
const logPtrReg = Blockly.LLVM.newReg();
code += `${logPtrReg} = load void (i32)*, void (i32)** @gLogIntProc\n`;
code += `call void ${logPtrReg}(i32 ${valueReg})\n`;
return code;
};
Blockly.LLVM.forBlock['current_datetime_utc'] = function(block) {
const structPtrReg = Blockly.LLVM.newReg();
const getUtcProcPtrReg = Blockly.LLVM.newReg();
let code = `
${structPtrReg} = alloca %struct.DateTimeUTC, align 8
${getUtcProcPtrReg} = load void (%struct.DateTimeUTC*)*, void (%struct.DateTimeUTC*)** @gGetCurrentDateTimeUTCProc\n
call void ${getUtcProcPtrReg}(%struct.DateTimeUTC* ${structPtrReg})
`;
return [code + structPtrReg, Blockly.LLVM.ORDER_ATOMIC];
};
Blockly.LLVM.forBlock['get_datetime_component'] = function(block) {
const datetimePtr = Blockly.LLVM.valueToCode(block, 'DATETIME', Blockly.LLVM.ORDER_ATOMIC) || 'null';
const component = block.getFieldValue('COMPONENT');
const timezone = block.getFieldValue('TIMEZONE');
const timezoneGlobalName = `@.str.timezone_${timezone.replace(/[^a-zA-Z0-9_]/g, '')}`;
const timezoneStringLength = new TextEncoder().encode(timezone).length + 1;
if (!Blockly.LLVM.generatedStrings.has(timezoneGlobalName)) {
Blockly.LLVM.globalDeclarations += `${timezoneGlobalName} = private unnamed_addr constant [${timezoneStringLength} x i8] c"${timezone}\\00", align 1\n`;
Blockly.LLVM.generatedStrings.add(timezoneGlobalName);
}
let componentIndex = 0;
switch (component) {
case 'YEAR': componentIndex = 0; break;
case 'MONTH': componentIndex = 1; break;
case 'DAY': componentIndex = 2; break;
case 'HOUR': componentIndex = 3; break;
case 'MINUTE': componentIndex = 4; break;
case 'SECOND': componentIndex = 5; break;
}
const resultReg = Blockly.LLVM.newReg();
const timezoneStringPtrReg = Blockly.LLVM.newReg();
const getComponentProcPtrReg = Blockly.LLVM.newReg();
let code = `
${timezoneStringPtrReg} = getelementptr inbounds [${timezoneStringLength} x i8], [${timezoneStringLength} x i8]* ${timezoneGlobalName}, i64 0, i64 0
${getComponentProcPtrReg} = load i32 (%struct.DateTimeUTC*, i32, i8*)*, i32 (%struct.DateTimeUTC*, i32, i8*)** @gGetComponentInZoneProc\n
${resultReg} = call i32 ${getComponentProcPtrReg}(%struct.DateTimeUTC* ${datetimePtr}, i32 ${componentIndex}, i8* ${timezoneStringPtrReg})
`;
return [code + resultReg, Blockly.LLVM.ORDER_ATOMIC];
};
const toolboxXml = `
<xml>
<category name="Variablen" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
<category name="Mathematik" colour="%{BKY_MATH_HUE}">
<block type="math_number"></block>
<block type="math_change">
<value name="DELTA"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
</block>
</category>
<category name="Host" colour="290">
<block type="host_log_integer"></block>
</category>
<category name="Datum &amp; Zeit" colour="120">
<block type="current_datetime_utc"></block>
<block type="get_datetime_component"></block>
</category>
</xml>`;
workspace = Blockly.inject('blocklyDiv', {
toolbox: toolboxXml,
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2
},
trashcan: true,
maxBlocks: 500,
scrollbars: true,
grid: {
spacing: 25,
length: 3,
colour: '#eee',
snap: true
},
comments: true,
disable: true,
collapse: true,
readOnly: false
});
// --- Code-Generierungs- und Post-Funktion (angepasst, um vorher zu speichern) ---
function generateAndPostCode() {
// 1. Arbeitsbereich speichern
const state = Blockly.serialization.workspaces.save(workspace);
localStorage.setItem('blocklyWorkspace', JSON.stringify(state));
// Optional: Konsolenmeldung zur automatischen Speicherung
// console.log("Arbeitsbereich automatisch gespeichert vor Code-Generierung.");
// 2. Code generieren
const code = Blockly.LLVM.workspaceToCode(workspace);
// 3. Code posten oder anzeigen
if (isWebView) {
if (window.chrome && window.chrome.webview) {
window.chrome.webview.postMessage(code);
}
} else {
document.getElementById('codeOutput').value = code;
console.log("Code in Konsole ausgegeben (nicht im WebView):");
console.log(code);
}
}
window.generateAndPostCode = generateAndPostCode; // Exponiere für Buttons
window.saveWorkspace = function() {
// Diese Funktion kann den Arbeitsbereich manuell speichern und einen Alert anzeigen
const state = Blockly.serialization.workspaces.save(workspace);
localStorage.setItem('blocklyWorkspace', JSON.stringify(state));
alert('Arbeitsbereich gespeichert!');
}
window.loadWorkspace = function() { const stateString = localStorage.getItem('blocklyWorkspace'); if (!stateString) { return; } const state = JSON.parse(stateString); Blockly.serialization.workspaces.load(state, workspace); }
// --- Dynamische Code-Aktualisierung / Posten (ruft generateAndPostCode auf) ---
function handleCodeUpdate(event) {
if (event.isUiEvent) return; // Nur Änderungen durch Blöcke, nicht UI-Interaktionen
generateAndPostCode(); // Dies triggert jetzt sowohl Speichern als auch Generieren/Posten
}
workspace.addChangeListener(handleCodeUpdate);
loadWorkspace();
// --- Bedingte UI-Elemente und Splitter-Logik ---
const outputContainer = document.getElementById('outputContainer');
const blocklyDiv = document.getElementById('blocklyDiv');
if (isWebView) {
// Wenn im WebView, outputContainer bleibt ausgeblendet (CSS default: display: none;)
// BlocklyDiv die gesamte Breite geben
blocklyDiv.style.width = '100%';
// Blockly-Workspace an neue Größe anpassen, mit einer kleinen Verzögerung für WebView
setTimeout(function() {
Blockly.svgResize(workspace);
}, 100); // 100ms Verzögerung, kann bei Bedarf angepasst werden.
// Entferne die Schaltflächen "Speichern" und "Laden", wenn im WebView
// da die Speicherung automatisch erfolgt und der Host den Code direkt bekommt.
const buttonBar = document.querySelector('.button-bar');
if (buttonBar) {
const saveButton = buttonBar.querySelector('button[onclick="saveWorkspace()"]');
const loadButton = buttonBar.querySelector('button[onclick="loadWorkspace()"]');
if (saveButton) saveButton.remove();
if (loadButton) loadButton.remove();
// Eventuell auch den "Code generieren und posten" Button entfernen, da es automatisch passiert
// oder umbenennen in "Aktualisieren" / "Neu senden"
const postButton = buttonBar.querySelector('button[onclick="generateAndPostCode()"]');
if (postButton) postButton.remove(); // Oder postButton.textContent = "Code senden";
}
} else {
// Wenn NICHT im WebView, outputContainer einblenden und Splitter-Logik initialisieren
outputContainer.style.display = 'flex'; // Einblenden als Flex-Container
const outputArea = document.getElementById('outputArea');
const splitter = document.getElementById('splitter');
const container = document.getElementById('container');
let isDragging = false;
splitter.addEventListener('mousedown', function(e) {
isDragging = true;
document.body.style.userSelect = 'none';
document.body.style.cursor = 'ew-resize';
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
const containerRect = container.getBoundingClientRect();
const splitterWidth = splitter.offsetWidth;
const outputAreaMarginLeft = parseInt(window.getComputedStyle(outputArea).marginLeft);
const totalContentWidth = containerRect.width - splitterWidth - outputAreaMarginLeft;
let newBlocklyWidth = e.clientX - containerRect.left;
const minBlocklyWidth = parseFloat(window.getComputedStyle(blocklyDiv).minWidth);
const minOutputAreaWidth = parseFloat(window.getComputedStyle(outputArea).minWidth);
if (newBlocklyWidth < minBlocklyWidth) {
newBlocklyWidth = minBlocklyWidth;
}
let newOutputAreaWidth = totalContentWidth - newBlocklyWidth;
if (newOutputAreaWidth < minOutputAreaWidth) {
newOutputAreaWidth = minOutputAreaWidth;
newBlocklyWidth = totalContentWidth - newOutputAreaWidth;
if (newBlocklyWidth < minBlocklyWidth) {
newBlocklyWidth = minBlocklyWidth;
}
}
blocklyDiv.style.width = `${newBlocklyWidth}px`;
outputArea.style.width = `${newOutputAreaWidth}px`;
Blockly.svgResize(workspace);
});
document.addEventListener('mouseup', function() {
isDragging = false;
document.body.style.userSelect = '';
document.body.style.cursor = '';
});
// Initiales Resize für Nicht-WebView: Setzt die initialen Breiten
// Basierend auf der Startbreite der outputArea (500px)
const initialOutputAreaWidth = 500; // Aus CSS initialer Wert
const totalFlexContentWidth = container.offsetWidth - splitter.offsetWidth - parseInt(window.getComputedStyle(outputArea).marginLeft);
let initialBlocklyWidth = totalFlexContentWidth - initialOutputAreaWidth;
if (initialBlocklyWidth < minBlocklyWidth) {
initialBlocklyWidth = minBlocklyWidth;
outputArea.style.width = `${totalFlexContentWidth - initialBlocklyWidth}px`;
} else {
outputArea.style.width = `${initialOutputAreaWidth}px`;
}
blocklyDiv.style.width = `${initialBlocklyWidth}px`;
Blockly.svgResize(workspace);
}
};
+116
View File
@@ -0,0 +1,116 @@
/* style.css */
/* Global box-sizing für konsistente Größenberechnung */
* {
box-sizing: border-box;
}
/* Allgemeine Styles für html und body, um den gesamten Viewport zu füllen */
html, body {
margin: 0;
padding: 0;
height: 100%; /* Stellt sicher, dass html und body die volle Viewport-Höhe einnehmen */
font-family: sans-serif;
overflow: hidden; /* Verhindert Scrollbalken auf der Hauptebene des Bodys */
background-color: #F8F8F8; /* Optionale Hintergrundfarbe für den gesamten Body, falls gewünscht */
}
/* Body als Flex-Container, um Inhalte vertikal zu stapeln */
body {
display: flex;
flex-direction: column;
}
/* Optional: Style für die Überschrift im Output-Bereich und allgemeine Überschriften/Paragraphen */
h1, h2, p {
margin: 0;
padding: 10px 0; /* Beispiel: Etwas vertikale Polsterung, keine horizontalen Ränder */
text-align: center; /* Überschriften zentrieren */
}
/* Styling für die Button-Leiste */
.button-bar {
margin-bottom: 0; /* Rand unten entfernen */
padding: 10px 0 0 0; /* Polsterung anpassen, um oberen Rand zu entfernen */
text-align: center; /* Buttons zentrieren */
}
.button-bar button {
margin: 0 5px; /* Kleiner Abstand zwischen den Buttons */
padding: 8px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #e0e0e0;
cursor: pointer;
}
.button-bar button:hover {
background-color: #d0d0d0;
}
/* Haupt-Container für Blockly-Workspace und Output-Bereich */
#container {
display: flex; /* Machen Sie den Container zu einem Flex-Container, um die Kinder nebeneinander anzuordnen */
flex-grow: 1; /* Lässt den Container den restlichen vertikalen Platz füllen */
padding: 0; /* Polsterung um den Inhalt herum vollständig entfernen */
box-sizing: border-box; /* Stellt sicher, dass Padding in der Größe enthalten ist */
}
/* Blockly-Workspace Bereich */
#blocklyDiv {
flex-grow: 1; /* Lässt BlocklyDiv den maximalen horizontalen Platz einnehmen */
min-width: 200px; /* Mindestbreite für Blockly, damit es nicht zu klein wird */
height: 100%; /* Füllt die Höhe des #container */
background-color: #FFF8E1; /* Sehr helles Beige für den Blockly-Hintergrund des Containers */
margin: 0; /* Rand entfernen */
}
/* WICHTIG: Regel für den Blockly SVG-Hintergrund */
#blocklyDiv .blocklySvg {
background-color: #FFF8E1; /* Hintergrundfarbe für das von Blockly generierte SVG-Element */
}
/* Optional: Sicherstellen, dass Blockly-Scrollbalken über anderen Elementen liegen */
#blocklyDiv .blocklyScrollbarHorizontal,
#blocklyDiv .blocklyScrollbarVertical {
z-index: 100;
}
/* Flex-Container für Splitter und Output-Bereich */
#outputContainer {
display: none; /* Standardmäßig ausgeblendet, wird bei Bedarf von JS eingeblendet */
flex-shrink: 0; /* Verhindert, dass dieser Container schrumpft, wenn nicht im WebView */
flex-basis: auto; /* Initial flexible Basis */
}
/* Splitter-Element */
#splitter {
width: 8px; /* Breite des Splitters */
background-color: #ccc; /* Farbe des Splitters */
cursor: ew-resize; /* Zeigt den "Ost-West-Größenänderung"-Cursor */
flex-shrink: 0; /* Verhindert, dass der Splitter schrumpft */
margin-left: 0; /* Abstand zum Blockly-Bereich entfernen */
margin-right: 0; /* Abstand zum Output-Bereich entfernen */
}
/* Output-Bereich (mit Code-Textarea) */
#outputArea {
display: flex;
flex-direction: column; /* Überschrift und Textarea vertikal stapeln */
min-width: 200px; /* Mindestbreite für die OutputArea */
width: 500px; /* Startbreite für den Output-Bereich, wird von JS überschrieben */
box-sizing: border-box;
margin: 0; /* Rand entfernen */
}
/* Textarea für den generierten Code */
#codeOutput {
flex-grow: 1; /* Lässt die Textarea die verbleibende Höhe in outputArea füllen */
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
white-space: pre-wrap;
border: none; /* Deaktiviert den Rand der Textarea */
resize: none; /* Deaktiviert die eingebaute Resize-Funktion der Textarea */
overflow: auto; /* Notwendig, damit Scrollbalken erscheinen, wenn der Inhalt die Größe überschreitet */
padding: 10px; /* Optional: Innenabstand für den Text, damit er nicht direkt am Rand klebt */
}