Blockly review
This commit is contained in:
@@ -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 & 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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user