Copy module.exports = ({ useEffect, useRef, api, usePlaceholders, useState, useLibraries }) => {
const blocklyDiv = useRef(null);
const workspace = useRef(null);
const [code, setCode] = useState('');
const [output, setOutput] = useState('');
const placeholders = usePlaceholders();
const { Blockly } = useLibraries();
useEffect(() => {
if (blocklyDiv.current && Blockly) {
// Create custom dark theme
const darkTheme = Blockly.Theme.defineTheme('dark', {
'base': Blockly.Themes.Classic,
'componentStyles': {
'workspaceBackgroundColour': '#1e1e1e',
'toolboxBackgroundColour': '#333333',
'toolboxForegroundColour': '#ffffff',
'flyoutBackgroundColour': '#252526',
'flyoutForegroundColour': '#cccccc',
'flyoutOpacity': 0.9,
'scrollbarColour': '#484848',
'insertionMarkerColour': '#ffffff',
'insertionMarkerOpacity': 0.3,
'markerColour': '#fff200',
'cursorColour': '#d0d0d0',
'selectedGlowColour': '#ffffff',
'selectedGlowOpacity': 0.3,
'replacementGlowColour': '#fff200',
'replacementGlowOpacity': 0.3
},
'blockStyles': {
'colour_blocks': {
'colourPrimary': '#a55b80',
'colourSecondary': '#ad73a6',
'colourTertiary': '#9c2d69'
},
'list_blocks': {
'colourPrimary': '#745ba5',
'colourSecondary': '#9085db',
'colourTertiary': '#6c4ce6'
},
'logic_blocks': {
'colourPrimary': '#5b80a5',
'colourSecondary': '#73a6ad',
'colourTertiary': '#2d699c'
},
'loop_blocks': {
'colourPrimary': '#5ba55b',
'colourSecondary': '#6bb36b',
'colourTertiary': '#498949'
},
'math_blocks': {
'colourPrimary': '#5b67a5',
'colourSecondary': '#6c82c7',
'colourTertiary': '#3e56a6'
},
'procedure_blocks': {
'colourPrimary': '#995ba5',
'colourSecondary': '#b373c7',
'colourTertiary': '#8c4ca6'
},
'text_blocks': {
'colourPrimary': '#5ba58c',
'colourSecondary': '#73c7a6',
'colourTertiary': '#4ca686'
},
'variable_blocks': {
'colourPrimary': '#a55b99',
'colourSecondary': '#c773b3',
'colourTertiary': '#a64c8c'
}
}
});
// Initialize Blockly workspace
workspace.current = Blockly.inject(blocklyDiv.current, {
theme: darkTheme,
toolbox: {
kind: 'categoryToolbox',
contents: [
{
kind: 'category',
name: 'Logic',
colour: 210,
contents: [
{
kind: 'block',
type: 'controls_if'
},
{
kind: 'block',
type: 'logic_compare'
},
{
kind: 'block',
type: 'logic_operation'
},
{
kind: 'block',
type: 'logic_negate'
},
{
kind: 'block',
type: 'logic_boolean'
}
]
},
{
kind: 'category',
name: 'Loops',
colour: 120,
contents: [
{
kind: 'block',
type: 'controls_repeat_ext'
},
{
kind: 'block',
type: 'controls_whileUntil'
},
{
kind: 'block',
type: 'controls_for'
}
]
},
{
kind: 'category',
name: 'Math',
colour: 230,
contents: [
{
kind: 'block',
type: 'math_number'
},
{
kind: 'block',
type: 'math_arithmetic'
},
{
kind: 'block',
type: 'math_single'
},
{
kind: 'block',
type: 'math_trig'
},
{
kind: 'block',
type: 'math_constant'
}
]
},
{
kind: 'category',
name: 'Text',
colour: 160,
contents: [
{
kind: 'block',
type: 'text'
},
{
kind: 'block',
type: 'text_join'
},
{
kind: 'block',
type: 'text_append'
},
{
kind: 'block',
type: 'text_length'
},
{
kind: 'block',
type: 'text_isEmpty'
}
]
},
{
kind: 'category',
name: 'Variables',
colour: 330,
custom: 'VARIABLE'
},
{
kind: 'category',
name: 'Functions',
colour: 290,
custom: 'PROCEDURE'
},
{
kind: 'category',
name: 'Console',
colour: 65,
contents: [
{
kind: 'block',
type: 'console_log'
}
]
}
]
},
grid: {
spacing: 20,
length: 3,
colour: '#ccc',
snap: true
},
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2
},
trashcan: true
});
// Define custom console log block
if (Blockly.Blocks) {
Blockly.Blocks['console_log'] = {
init: function() {
this.appendValueInput('MESSAGE')
.setCheck(null)
.appendField('console log');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(65);
this.setTooltip('Log a message to the console');
this.setHelpUrl('');
}
};
// JavaScript code generator for console log
if (Blockly.JavaScript) {
Blockly.JavaScript['console_log'] = function(block) {
const message = Blockly.JavaScript.valueToCode(block, 'MESSAGE', Blockly.JavaScript.ORDER_ATOMIC);
return `console.log(${message});\n`;
};
}
}
// Listen for workspace changes
workspace.current.addChangeListener(() => {
generateCode();
});
api.console.log('Blockly workspace initialized');
}
return () => {
if (workspace.current) {
workspace.current.dispose();
}
};
}, []);
const generateCode = () => {
if (workspace.current && Blockly.JavaScript) {
const generatedCode = Blockly.JavaScript.workspaceToCode(workspace.current);
setCode(generatedCode);
}
};
const runCode = () => {
if (!code.trim()) {
setOutput('No code to run');
return;
}
try {
// Capture console.log output
const originalLog = console.log;
let capturedOutput = '';
console.log = (...args) => {
capturedOutput += args.join(' ') + '\n';
originalLog(...args);
};
// Execute the generated code
eval(code);
// Restore original console.log
console.log = originalLog;
setOutput(capturedOutput || 'Code executed successfully (no output)');
api.console.log('Blockly code executed:', code);
} catch (error) {
setOutput(`Error: ${error.message}`);
api.console.error('Blockly execution error:', error);
}
};
const clearWorkspace = () => {
if (workspace.current) {
workspace.current.clear();
setCode('');
setOutput('');
}
};
const saveWorkspace = async () => {
if (workspace.current) {
try {
const xml = Blockly.Xml.workspaceToDom(workspace.current);
const xmlText = Blockly.Xml.domToPrettyText(xml);
const result = await api.nexomaker.yaml.write({
projectId: placeholders.projectId,
fileName: "blockly-workspace.yml",
jsonObject: {
workspace: xmlText,
code: code,
saved_at: placeholders.timestamp()
},
mode: 'item'
});
api.console.log('Blockly workspace saved:', result);
setOutput('Workspace saved successfully!');
} catch (error) {
api.console.error('Failed to save workspace:', error);
setOutput(`Failed to save: ${error.message}`);
}
}
};
return (
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
<div style={{ padding: '10px', borderBottom: '1px solid #ccc' }}>
<h1>Blockly Test Environment</h1>
<p>Project ID: {placeholders.projectId}</p>
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
<button onClick={runCode} style={{ padding: '5px 10px' }}>Run Code</button>
<button onClick={clearWorkspace} style={{ padding: '5px 10px' }}>Clear Workspace</button>
<button onClick={saveWorkspace} style={{ padding: '5px 10px' }}>Save Workspace</button>
<button onClick={generateCode} style={{ padding: '5px 10px' }}>Generate Code</button>
</div>
</div>
<div style={{ display: 'flex', flex: 1 }}>
{/* Blockly workspace */}
<div
ref={blocklyDiv}
style={{
width: '60%',
height: '100%',
border: '1px solid #ccc'
}}
/>
{/* Code and output panel */}
<div style={{ width: '40%', display: 'flex', flexDirection: 'column' }}>
{/* Generated code */}
<div style={{ flex: 1, padding: '10px', borderLeft: '1px solid #ccc' }}>
<h3>Generated JavaScript:</h3>
<pre style={{
backgroundColor: '#f5f5f5',
padding: '10px',
overflow: 'auto',
fontSize: '12px',
height: '200px',
border: '1px solid #ddd'
}}>
{code || '// Drag blocks to generate code'}
</pre>
</div>
{/* Output */}
<div style={{ flex: 1, padding: '10px', borderLeft: '1px solid #ccc', borderTop: '1px solid #ccc' }}>
<h3>Output:</h3>
<pre style={{
backgroundColor: '#f0f8ff',
padding: '10px',
overflow: 'auto',
fontSize: '12px',
height: '200px',
border: '1px solid #ddd'
}}>
{output || '// Output will appear here'}
</pre>
</div>
</div>
</div>
</div>
);
};