@adcops/autocore-react 3.0.22 → 3.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import Blockly from "blockly";
2
1
  import React from 'react';
2
+ import Blockly from "blockly";
3
3
  import "./BlocklyEditor.css";
4
4
  /**
5
5
  * Properties for the Blockly Editor.
@@ -9,7 +9,7 @@ interface BlocklyEditorProps {
9
9
  * The initial XML representation of the Blockly workspace. Can be undefined
10
10
  * if the workspace should start empty.
11
11
  */
12
- initialXml: string | undefined;
12
+ initialXml?: string | undefined;
13
13
  /**
14
14
  * The configuration definition for the Blockly Toolbox.
15
15
  */
@@ -30,7 +30,7 @@ interface BlocklyEditorProps {
30
30
  * Callback fired when the Blockly workspace content changes.
31
31
  * Note: This does not directly reflect changes to the XML or the code.
32
32
  */
33
- onWorkspaceChanged?: () => void;
33
+ onWorkspaceChanged?: (workspace: Blockly.WorkspaceSvg | undefined) => void;
34
34
  /**
35
35
  * Callback fired when the XML representation of the workspace changes.
36
36
  * @param xml The updated XML representation of the workspace.
@@ -41,35 +41,11 @@ interface BlocklyEditorProps {
41
41
  * @param code The updated generated code.
42
42
  */
43
43
  onCodeChanged?: (code: string) => void;
44
- }
45
- /**
46
- * State of the BlocklyEditor
47
- */
48
- interface BlocklyEditorState {
49
- /**
50
- * A name for the Blockly editor, potentially used for identification.
51
- */
52
- name: string;
53
- /**
54
- * The current XML representation of the workspace.
55
- */
56
- xml: string;
57
44
  /**
58
- * The current generated code (e.g., Python).
45
+ * Callback fired when an error occurs.
46
+ * Includes an error message.
59
47
  */
60
- code: string;
61
- /**
62
- * The height of the first parent container with
63
- * classname "blockly-container". It is important to set
64
- * a parent container with this class name for auto-sizing to work!
65
- */
66
- parentHeight: number;
67
- /**
68
- * The width of the first parent container with
69
- * classname "blockly-container". It is important to set
70
- * a parent container with this class name for auto-sizing to work!
71
- */
72
- parentWidth: number;
48
+ onError?: (message: string) => void;
73
49
  }
74
50
  /**
75
51
  * A view that contains the blockly editor, wrapping the react-blockly BlocklyWorkspace
@@ -83,30 +59,52 @@ interface BlocklyEditorState {
83
59
  *
84
60
  * Right now, the editor only supports generating python code, but a feature to specify the
85
61
  * generator for different languages is planned.
62
+ *
63
+ * ## Usage Example
64
+ *
65
+ * ```tsx
66
+ * import React, { useRef } from 'react';
67
+ * import BlocklyEditor from './BlocklyEditor';
68
+ * import { StandardToolbox } from './toolbox';
69
+ *
70
+ * const App: React.FC = () => {
71
+ * const blocklyEditorRef = useRef<{ newBlockly: () => void; loadBlockly: (xmlString: string) => void }>(null);
72
+ *
73
+ * const handleNew = () => {
74
+ * if (blocklyEditorRef.current) {
75
+ * blocklyEditorRef.current.newBlockly();
76
+ * }
77
+ * };
78
+ *
79
+ * const handleLoad = (xmlString: string) => {
80
+ * if (blocklyEditorRef.current) {
81
+ * blocklyEditorRef.current.loadBlockly(xmlString);
82
+ * }
83
+ * };
84
+ *
85
+ * return (
86
+ * <div className="blockly-container" style={{ height: '500px' }}>
87
+ * <BlocklyEditor
88
+ * ref={blocklyEditorRef}
89
+ * initialXml="<xml xmlns='http://www.w3.org/1999/xhtml'></xml>"
90
+ * toolbox={StandardToolbox}
91
+ * widthAdjustment={0}
92
+ * heightAdjustment={0}
93
+ * onWorkspaceChanged={(workspace) => console.log('Workspace changed:', workspace)}
94
+ * onXmlChanged={(xml) => console.log('XML changed:', xml)}
95
+ * onCodeChanged={(code) => console.log('Code changed:', code)}
96
+ * onError={(message) => console.error('Error:', message)}
97
+ * />
98
+ * <button onClick={handleNew}>New</button>
99
+ * <button onClick={() => handleLoad('<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_if" x="10" y="10"></block></xml>')}>Load</button>
100
+ * </div>
101
+ * );
102
+ * };
103
+ *
104
+ * export default App;
105
+ * ```
86
106
  */
87
- export declare class BlocklyEditor extends React.Component<BlocklyEditorProps, BlocklyEditorState> {
88
- static defaultProps: {
89
- initialXml: undefined;
90
- widthAdjustment: number;
91
- heightAdjustment: number;
92
- onWorkspaceChanged: undefined;
93
- onXmlChanged: undefined;
94
- onCodeChanged: undefined;
95
- };
96
- private ref;
97
- /**
98
- * Constructor
99
- */
100
- constructor(props: BlocklyEditorProps);
101
- componentDidMount(): void;
102
- componentDidUpdate(prevProps: BlocklyEditorProps): void;
103
- componentWillUnmount(): void;
104
- updateParentSize: () => void;
105
- private onWorkspaceChanged;
106
- private setXml;
107
- private onInject;
108
- render(): import("react/jsx-runtime").JSX.Element;
109
- }
107
+ export declare const BlocklyEditor: React.ForwardRefExoticComponent<BlocklyEditorProps & React.RefAttributes<unknown>>;
110
108
  export default BlocklyEditor;
111
109
  /**
112
110
  * Creates a custom Blockly toolbox XML string by inserting additional XML before and after
@@ -1 +1 @@
1
- import{jsx as _jsx,Fragment as _Fragment}from"react/jsx-runtime";import{pythonGenerator}from"blockly/python";import{BlocklyWorkspace}from"react-blockly";import React from"react";import"./BlocklyEditor.css";export class BlocklyEditor extends React.Component{constructor(e){super(e),Object.defineProperty(this,"ref",{enumerable:!0,configurable:!0,writable:!0,value:React.createRef()}),Object.defineProperty(this,"updateParentSize",{enumerable:!0,configurable:!0,writable:!0,value:()=>{if(this.ref.current){let e=0,n=0,t=this.ref.current.parentElement;for(;null!=t&&(e=t.offsetWidth,n=t.offsetHeight,!(t.className.indexOf("blockly-container")>=0));)t=t.parentElement;e<300&&(e=300),n<300&&(n=300),this.setState({parentWidth:.9*e+this.props.widthAdjustment,parentHeight:.9*n+this.props.heightAdjustment})}}}),Object.defineProperty(this,"onWorkspaceChanged",{enumerable:!0,configurable:!0,writable:!0,value:e=>{const n=pythonGenerator.workspaceToCode(e);this.setState({code:n}),void 0!==this.props.onWorkspaceChanged&&null!==this.props.onWorkspaceChanged&&this.props.onWorkspaceChanged(),void 0!==this.props.onCodeChanged&&null!==this.props.onCodeChanged&&this.props.onCodeChanged(n)}}),Object.defineProperty(this,"setXml",{enumerable:!0,configurable:!0,writable:!0,value:e=>{this.setState({xml:e}),void 0!==this.props.onXmlChanged&&null!==this.props.onXmlChanged&&this.props.onXmlChanged(e)}}),Object.defineProperty(this,"onInject",{enumerable:!0,configurable:!0,writable:!0,value:e=>{}}),this.state={name:"",xml:"",code:"",parentWidth:0,parentHeight:0}}componentDidMount(){this.updateParentSize(),window.addEventListener("resize",this.updateParentSize)}componentDidUpdate(e){}componentWillUnmount(){window.removeEventListener("resize",this.updateParentSize)}render(){return _jsx(_Fragment,{children:_jsx(BlocklyWorkspace,{className:"fill-height",toolboxConfiguration:this.props.toolbox,initialXml:this.props.initialXml,onWorkspaceChange:this.onWorkspaceChanged,onXmlChange:this.setXml,onInject:this.onInject,workspaceConfiguration:{grid:{spacing:20,length:3,colour:"#ccc",snap:!0}}})})}}Object.defineProperty(BlocklyEditor,"defaultProps",{enumerable:!0,configurable:!0,writable:!0,value:{initialXml:void 0,widthAdjustment:0,heightAdjustment:0,onWorkspaceChanged:void 0,onXmlChanged:void 0,onCodeChanged:void 0}});export default BlocklyEditor;export const createCustomToolbox=(e,n)=>{const t="\x3c!-- END: Built-in Blockly Blocks //--\x3e",a=StandardToolbox.indexOf("\x3c!-- BEGIN: Built-in Blockly Blocks //--\x3e"),l=StandardToolbox.indexOf(t);if(-1===a||-1===l)throw new Error("Could not find markers in standard toolbox");return StandardToolbox.slice(0,a)+(e||"")+StandardToolbox.slice(a,l+39)+(n||"")+StandardToolbox.slice(l+39+1)};export const StandardToolbox='\n <xml id="toolbox" style="display: none">\n \x3c!-- BEGIN: Built-in Blockly Blocks //--\x3e\n <category name="Logic" categorystyle="logic_category">\n <block type="controls_if"></block>\n <block type="logic_compare"></block>\n <block type="logic_operation"></block>\n <block type="logic_negate"></block>\n <block type="logic_boolean"></block>\n <block type="logic_null" disabled="true"></block>\n <block type="logic_ternary"></block>\n </category>\n <category name="Loops" categorystyle="loop_category">\n <block type="controls_repeat_ext">\n <value name="TIMES">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n </block>\n <block type="controls_repeat" disabled="true"></block>\n <block type="controls_whileUntil"></block>\n <block type="controls_for">\n <value name="FROM">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="TO">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n <value name="BY">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n <block type="controls_forEach"></block>\n <block type="controls_flow_statements"></block>\n </category>\n <category name="Math" categorystyle="math_category">\n <block type="math_number" gap="32">\n <field name="NUM">123</field>\n </block>\n <block type="math_arithmetic">\n <value name="A">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="B">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n <block type="math_single">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">9</field>\n </shadow>\n </value>\n </block>\n <block type="math_trig">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">45</field>\n </shadow>\n </value>\n </block>\n <block type="math_constant"></block>\n <block type="math_number_property">\n <value name="NUMBER_TO_CHECK">\n <shadow type="math_number">\n <field name="NUM">0</field>\n </shadow>\n </value>\n </block>\n <block type="math_round">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">3.1</field>\n </shadow>\n </value>\n </block>\n <block type="math_on_list"></block>\n <block type="math_modulo">\n <value name="DIVIDEND">\n <shadow type="math_number">\n <field name="NUM">64</field>\n </shadow>\n </value>\n <value name="DIVISOR">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n </block>\n <block type="math_constrain">\n <value name="VALUE">\n <shadow type="math_number">\n <field name="NUM">50</field>\n </shadow>\n </value>\n <value name="LOW">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="HIGH">\n <shadow type="math_number">\n <field name="NUM">100</field>\n </shadow>\n </value>\n </block>\n <block type="math_random_int">\n <value name="FROM">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="TO">\n <shadow type="math_number">\n <field name="NUM">100</field>\n </shadow>\n </value>\n </block>\n <block type="math_random_float"></block>\n <block type="math_atan2">\n <value name="X">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="Y">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n </category>\n <category name="Text" categorystyle="text_category">\n <block type="text"></block>\n <block type="text_multiline"></block>\n <block type="text_join"></block>\n <block type="text_append">\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_length">\n <value name="VALUE">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_isEmpty">\n <value name="VALUE">\n <shadow type="text">\n <field name="TEXT"></field>\n </shadow>\n </value>\n </block>\n <block type="text_indexOf">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n <value name="FIND">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_charAt">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n </block>\n <block type="text_getSubstring">\n <value name="STRING">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n </block>\n <block type="text_changeCase">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_trim">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_count">\n <value name="SUB">\n <shadow type="text"></shadow>\n </value>\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_replace">\n <value name="FROM">\n <shadow type="text"></shadow>\n </value>\n <value name="TO">\n <shadow type="text"></shadow>\n </value>\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_reverse">\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <label text="Input/Output:" web-class="ioLabel"></label>\n <block type="text_print">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_prompt_ext">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n </category>\n <category name="Lists" categorystyle="list_category">\n <block type="lists_create_with">\n <mutation items="0"></mutation>\n </block>\n <block type="lists_create_with"></block>\n <block type="lists_repeat">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">5</field>\n </shadow>\n </value>\n </block>\n <block type="lists_length"></block>\n <block type="lists_isEmpty"></block>\n <block type="lists_indexOf">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_getIndex">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_setIndex">\n <value name="LIST">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_getSublist">\n <value name="LIST">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_split">\n <value name="DELIM">\n <shadow type="text">\n <field name="TEXT">,</field>\n </shadow>\n </value>\n </block>\n <block type="lists_sort"></block>\n <block type="lists_reverse"></block>\n </category>\n <category name="Variables" categorystyle="variable_category" custom="VARIABLE"></category>\n <category name="Functions" categorystyle="procedure_category" custom="PROCEDURE"></category>\n\n \x3c!-- END: Built-in Blockly Blocks //--\x3e\n\n </xml>\n';
1
+ import{jsx as _jsx}from"react/jsx-runtime";import React,{forwardRef,useImperativeHandle,useCallback,useState}from"react";import Blockly from"blockly";import{pythonGenerator}from"blockly/python";import{BlocklyWorkspace}from"react-blockly";import"./BlocklyEditor.css";export const BlocklyEditor=forwardRef(((e,n)=>{const[l,t]=useState(""),[a,o]=useState(""),[c,d]=useState(0),[s,i]=useState(0),[m,r]=useState(void 0),b=React.createRef();useImperativeHandle(n,(()=>({newBlockly(){m&&m.clear()},loadBlockly(n){if(m)try{m.clear();const e=Blockly.utils.xml.textToDom(n);Blockly.Xml.domToWorkspace(e,m)}catch(n){e.onError&&e.onError(`${n}`)}}}))),React.useEffect((()=>(y(),window.addEventListener("resize",y),()=>{window.removeEventListener("resize",y)})),[]);const y=useCallback((()=>{if(b.current){let n=0,l=0,t=b.current.parentElement;for(;null!==t&&(n=t.offsetWidth,l=t.offsetHeight,!(t.className.indexOf("blockly-container")>=0));)t=t.parentElement;n<300&&(n=300),l<300&&(l=300),d(.9*n+e.widthAdjustment),i(.9*l+e.heightAdjustment)}}),[e.widthAdjustment,e.heightAdjustment]);return _jsx("div",{ref:b,style:{width:c,height:s},children:_jsx(BlocklyWorkspace,{className:"fill-height",toolboxConfiguration:e.toolbox,initialXml:e.initialXml,onWorkspaceChange:n=>{const l=pythonGenerator.workspaceToCode(n);o(l),e.onWorkspaceChanged&&e.onWorkspaceChanged(n),e.onCodeChanged&&e.onCodeChanged(l)},onXmlChange:n=>{t(n),void 0!==e.onXmlChanged&&null!==e.onXmlChanged&&e.onXmlChanged(n)},onInject:e=>{r(e)},workspaceConfiguration:{grid:{spacing:20,length:3,colour:"#ccc",snap:!0}}})})}));export default BlocklyEditor;export const createCustomToolbox=(e,n)=>{const l="\x3c!-- END: Built-in Blockly Blocks //--\x3e",t=StandardToolbox.indexOf("\x3c!-- BEGIN: Built-in Blockly Blocks //--\x3e"),a=StandardToolbox.indexOf(l);if(-1===t||-1===a)throw new Error("Could not find markers in standard toolbox");return StandardToolbox.slice(0,t)+(e||"")+StandardToolbox.slice(t,a+39)+(n||"")+StandardToolbox.slice(a+39+1)};export const StandardToolbox='\n <xml id="toolbox" style="display: none">\n \x3c!-- BEGIN: Built-in Blockly Blocks //--\x3e\n <category name="Logic" categorystyle="logic_category">\n <block type="controls_if"></block>\n <block type="logic_compare"></block>\n <block type="logic_operation"></block>\n <block type="logic_negate"></block>\n <block type="logic_boolean"></block>\n <block type="logic_null" disabled="true"></block>\n <block type="logic_ternary"></block>\n </category>\n <category name="Loops" categorystyle="loop_category">\n <block type="controls_repeat_ext">\n <value name="TIMES">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n </block>\n <block type="controls_repeat" disabled="true"></block>\n <block type="controls_whileUntil"></block>\n <block type="controls_for">\n <value name="FROM">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="TO">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n <value name="BY">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n <block type="controls_forEach"></block>\n <block type="controls_flow_statements"></block>\n </category>\n <category name="Math" categorystyle="math_category">\n <block type="math_number" gap="32">\n <field name="NUM">123</field>\n </block>\n <block type="math_arithmetic">\n <value name="A">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="B">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n <block type="math_single">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">9</field>\n </shadow>\n </value>\n </block>\n <block type="math_trig">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">45</field>\n </shadow>\n </value>\n </block>\n <block type="math_constant"></block>\n <block type="math_number_property">\n <value name="NUMBER_TO_CHECK">\n <shadow type="math_number">\n <field name="NUM">0</field>\n </shadow>\n </value>\n </block>\n <block type="math_round">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">3.1</field>\n </shadow>\n </value>\n </block>\n <block type="math_on_list"></block>\n <block type="math_modulo">\n <value name="DIVIDEND">\n <shadow type="math_number">\n <field name="NUM">64</field>\n </shadow>\n </value>\n <value name="DIVISOR">\n <shadow type="math_number">\n <field name="NUM">10</field>\n </shadow>\n </value>\n </block>\n <block type="math_constrain">\n <value name="VALUE">\n <shadow type="math_number">\n <field name="NUM">50</field>\n </shadow>\n </value>\n <value name="LOW">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="HIGH">\n <shadow type="math_number">\n <field name="NUM">100</field>\n </shadow>\n </value>\n </block>\n <block type="math_random_int">\n <value name="FROM">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="TO">\n <shadow type="math_number">\n <field name="NUM">100</field>\n </shadow>\n </value>\n </block>\n <block type="math_random_float"></block>\n <block type="math_atan2">\n <value name="X">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n <value name="Y">\n <shadow type="math_number">\n <field name="NUM">1</field>\n </shadow>\n </value>\n </block>\n </category>\n <category name="Text" categorystyle="text_category">\n <block type="text"></block>\n <block type="text_multiline"></block>\n <block type="text_join"></block>\n <block type="text_append">\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_length">\n <value name="VALUE">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_isEmpty">\n <value name="VALUE">\n <shadow type="text">\n <field name="TEXT"></field>\n </shadow>\n </value>\n </block>\n <block type="text_indexOf">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n <value name="FIND">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_charAt">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n </block>\n <block type="text_getSubstring">\n <value name="STRING">\n <block type="variables_get">\n <field name="VAR">text</field>\n </block>\n </value>\n </block>\n <block type="text_changeCase">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_trim">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_count">\n <value name="SUB">\n <shadow type="text"></shadow>\n </value>\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_replace">\n <value name="FROM">\n <shadow type="text"></shadow>\n </value>\n <value name="TO">\n <shadow type="text"></shadow>\n </value>\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <block type="text_reverse">\n <value name="TEXT">\n <shadow type="text"></shadow>\n </value>\n </block>\n <label text="Input/Output:" web-class="ioLabel"></label>\n <block type="text_print">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n <block type="text_prompt_ext">\n <value name="TEXT">\n <shadow type="text">\n <field name="TEXT">abc</field>\n </shadow>\n </value>\n </block>\n </category>\n <category name="Lists" categorystyle="list_category">\n <block type="lists_create_with">\n <mutation items="0"></mutation>\n </block>\n <block type="lists_create_with"></block>\n <block type="lists_repeat">\n <value name="NUM">\n <shadow type="math_number">\n <field name="NUM">5</field>\n </shadow>\n </value>\n </block>\n <block type="lists_length"></block>\n <block type="lists_isEmpty"></block>\n <block type="lists_indexOf">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_getIndex">\n <value name="VALUE">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_setIndex">\n <value name="LIST">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_getSublist">\n <value name="LIST">\n <block type="variables_get">\n <field name="VAR">list</field>\n </block>\n </value>\n </block>\n <block type="lists_split">\n <value name="DELIM">\n <shadow type="text">\n <field name="TEXT">,</field>\n </shadow>\n </value>\n </block>\n <block type="lists_sort"></block>\n <block type="lists_reverse"></block>\n </category>\n <category name="Variables" categorystyle="variable_category" custom="VARIABLE"></category>\n <category name="Functions" categorystyle="procedure_category" custom="PROCEDURE"></category>\n\n \x3c!-- END: Built-in Blockly Blocks //--\x3e\n\n </xml>\n';
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
+ import { InputTextProps } from 'primereact/inputtext';
2
3
  import { EventEmitterContext } from "../core/EventEmitterContext.js";
3
4
  import { KeyFilterType } from "primereact/keyfilter";
4
5
  /**
5
6
  * Properties of the component.
6
7
  */
7
- interface TextInputProps {
8
+ interface TextInputProps extends Omit<InputTextProps, "prefix"> {
8
9
  /**
9
10
  * The label for the field.
10
11
  */
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React from"react";import{InputText}from"primereact/inputtext";import{Button}from"primereact/button";import{EventEmitterContext}from"../core/EventEmitterContext.js";export class TextInput extends React.Component{constructor(t){super(t),Object.defineProperty(this,"validateValue",{enumerable:!0,configurable:!0,writable:!0,value:t=>void 0===this.props.validator||null===this.props.validator||this.props.validator.test(t)}),this.state={entryValue:t.value,isValid:void 0!==t.value&&this.validateValue(t.value),editing:!1}}componentDidMount(){}componentDidUpdate(t){t.value!==this.props.value&&this.setState({entryValue:this.props.value,editing:!1})}onAcceptValue(){void 0!==this.state.entryValue&&this.validateValue(this.state.entryValue)?(this.setState({isValid:!0,editing:!1}),this.props.onValueChanged&&this.props.onValueChanged(this.state.entryValue),void 0!==this.props.dispatchTopic&&this.context.dispatch({topic:this.props.dispatchTopic,payload:this.state.entryValue})):this.setState({isValid:!1})}onResetValue(){this.setState({entryValue:this.props.value,isValid:void 0!==this.props.value&&this.validateValue(this.props.value),editing:!1})}render(){return _jsxs("div",{children:[_jsxs("div",{className:"p-inputgroup flex-1",children:[_jsx("span",{className:"p-inputgroup-addon",children:this.props.label}),void 0!==this.props.prefix&&_jsx("span",{className:"p-inputgroup-addon",children:this.props.prefix}),_jsx(InputText,{keyfilter:this.props.keyFilter,placeholder:this.props.placeholder,value:this.state.entryValue,onChange:t=>{this.setState({entryValue:t.target.value,editing:!0})},className:this.state.isValid?"":"p-invalid",onKeyDown:t=>{"Enter"===t.key?this.onAcceptValue():"Escape"===t.key&&this.onResetValue()},disabled:this.props.disabled}),void 0!==this.props.suffix&&_jsx("span",{className:"p-inputgroup-addon",children:this.props.suffix}),_jsx(Button,{icon:"pi pi-check",disabled:this.props.disabled||!this.state.editing,className:"p-button-success",onClick:()=>this.onAcceptValue(),visible:this.state.editing}),_jsx(Button,{icon:"pi pi-times",disabled:this.props.disabled||!this.state.editing,className:"p-button-danger",onClickCapture:()=>this.onResetValue(),visible:this.state.editing})]}),void 0!==this.props.description&&_jsx("small",{children:this.props.description})]})}}Object.defineProperty(TextInput,"contextType",{enumerable:!0,configurable:!0,writable:!0,value:EventEmitterContext}),Object.defineProperty(TextInput,"defaultProps",{enumerable:!0,configurable:!0,writable:!0,value:{label:"",value:void 0,keyFilter:void 0,writeTopic:void 0,onValueChanged:void 0,description:void 0,prefix:void 0,suffix:void 0,disabled:!1,dispatchTopic:void 0,placeholder:void 0,validator:void 0}});
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React from"react";import{InputText}from"primereact/inputtext";import{Button}from"primereact/button";import{EventEmitterContext}from"../core/EventEmitterContext.js";export class TextInput extends React.Component{constructor(t){super(t),Object.defineProperty(this,"validateValue",{enumerable:!0,configurable:!0,writable:!0,value:t=>void 0===this.props.validator||null===this.props.validator||this.props.validator.test(t)}),this.state={entryValue:t.value,isValid:void 0!==t.value&&this.validateValue(t.value),editing:!1}}componentDidMount(){}componentDidUpdate(t){t.value!==this.props.value&&this.setState({entryValue:this.props.value,editing:!1})}onAcceptValue(){void 0!==this.state.entryValue&&this.validateValue(this.state.entryValue)?(this.setState({isValid:!0,editing:!1}),this.props.onValueChanged&&this.props.onValueChanged(this.state.entryValue),void 0!==this.props.dispatchTopic&&this.context.dispatch({topic:this.props.dispatchTopic,payload:this.state.entryValue})):this.setState({isValid:!1})}onResetValue(){this.setState({entryValue:this.props.value,isValid:void 0!==this.props.value&&this.validateValue(this.props.value),editing:!1})}render(){const{prefix:t,...e}=this.props;return _jsxs("div",{children:[_jsxs("div",{className:"p-inputgroup flex-1",children:[_jsx("span",{className:"p-inputgroup-addon",children:this.props.label}),void 0!==this.props.prefix&&_jsx("span",{className:"p-inputgroup-addon",children:this.props.prefix}),_jsx(InputText,{...e,keyfilter:this.props.keyFilter,placeholder:this.props.placeholder,value:this.state.entryValue,onChange:t=>{this.setState({entryValue:t.target.value,editing:!0})},className:this.state.isValid?"":"p-invalid",onKeyDown:t=>{"Enter"===t.key?this.onAcceptValue():"Escape"===t.key&&this.onResetValue()},disabled:this.props.disabled}),void 0!==this.props.suffix&&_jsx("span",{className:"p-inputgroup-addon",children:this.props.suffix}),_jsx(Button,{icon:"pi pi-check",disabled:this.props.disabled||!this.state.editing,className:"p-button-success",onClick:()=>this.onAcceptValue(),visible:this.state.editing}),_jsx(Button,{icon:"pi pi-times",disabled:this.props.disabled||!this.state.editing,className:"p-button-danger",onClickCapture:()=>this.onResetValue(),visible:this.state.editing})]}),void 0!==this.props.description&&_jsx("small",{children:this.props.description})]})}}Object.defineProperty(TextInput,"contextType",{enumerable:!0,configurable:!0,writable:!0,value:EventEmitterContext}),Object.defineProperty(TextInput,"defaultProps",{enumerable:!0,configurable:!0,writable:!0,value:{label:"",value:void 0,keyFilter:void 0,writeTopic:void 0,onValueChanged:void 0,description:void 0,prefix:void 0,suffix:void 0,disabled:!1,dispatchTopic:void 0,placeholder:void 0,validator:void 0}});
@@ -47,10 +47,11 @@
47
47
  * to the console when accepted.
48
48
  */
49
49
  import React from 'react';
50
+ import { InputNumberProps } from 'primereact/inputnumber';
50
51
  /**
51
52
  * Properties of the ValueInput component.
52
53
  */
53
- interface ValueInputProps {
54
+ interface ValueInputProps extends Omit<InputNumberProps, 'value'> {
54
55
  /**
55
56
  * The label for the ValueInput field.
56
57
  */
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import{useState,useRef,useEffect,useContext}from"react";import{InputNumber}from"primereact/inputnumber";import{EventEmitterContext}from"../core/EventEmitterContext";import{Button}from"primereact/button";export const ValueInput=({label:e="",value:t=null,min:s,max:i,minPrecision:n=0,maxPrecision:o=3,mode:a="decimal",currency:u="USD",prefix:l,suffix:r,showButtons:c=!1,step:p=1,locale:m="en-US",description:d,disabled:x=!1,dispatchTopic:f,placeholder:v,onValueChanged:b})=>{const[h,j]=useState(t),[C,E]=useState(t),[_,S]=useState(t),[g,y]=useState(!1),[N,k]=useState(!1),B=useRef(null),D=useContext(EventEmitterContext);useEffect((()=>{null!==_?(E(_),S(null),k(!1)):t!==C&&(E(t),j(t),y(!1),k(!1))}),[t,C]);const F=()=>{var e;g&&null!==h&&(e=h,void 0!==i&&e>i||void 0!==s&&e<s?k(!0):(E(h),y(!1),b?.(h),k(!1),f&&D.dispatch({topic:f,payload:h})))},I=()=>{g&&(j(null),E(null),y(!1),k(!1))};return _jsxs("div",{children:[_jsxs("div",{className:"p-inputgroup flex-1",children:[_jsx("span",{className:"p-inputgroup-addon",children:e}),_jsx(InputNumber,{ref:B,invalid:N,min:s,max:i,minFractionDigits:n,maxFractionDigits:o,mode:a,prefix:l,suffix:r,showButtons:c,step:p,placeholder:v,value:C,onChange:e=>{return t=e.value,g||(S(C),y(!0)),void j(t);var t},locale:m,currency:u,onKeyDown:e=>{"Enter"===e.key?F():"Escape"===e.key&&I()},disabled:x}),_jsx(Button,{icon:"pi pi-check",disabled:x||!g,className:"p-button-success",onClick:()=>F(),visible:g,size:"small",autoFocus:!1}),_jsx(Button,{icon:"pi pi-times",disabled:x||!g,className:"p-button-danger",onClickCapture:()=>I(),visible:g,size:"small",autoFocus:!1})]}),d&&_jsx("small",{children:d})]})};export default ValueInput;
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import{useState,useRef,useEffect,useContext}from"react";import{InputNumber}from"primereact/inputnumber";import{EventEmitterContext}from"../core/EventEmitterContext";import{Button}from"primereact/button";export const ValueInput=({label:e="",value:t=null,min:s,max:i,minPrecision:n=0,maxPrecision:o=3,mode:a="decimal",currency:u="USD",prefix:l,suffix:r,showButtons:c=!1,step:p=1,locale:m="en-US",description:d,disabled:x=!1,dispatchTopic:f,placeholder:v,onValueChanged:b,...h})=>{const[j,C]=useState(t),[E,_]=useState(t),[S,g]=useState(t),[y,N]=useState(!1),[k,B]=useState(!1),D=useRef(null),F=useContext(EventEmitterContext);useEffect((()=>{null!==S?(_(S),g(null),B(!1)):t!==E&&(_(t),C(t),N(!1),B(!1))}),[t,E]);const I=()=>{var e;y&&null!==j&&(e=j,void 0!==i&&e>i||void 0!==s&&e<s?B(!0):(_(j),N(!1),b?.(j),B(!1),f&&F.dispatch({topic:f,payload:j})))},w=()=>{y&&(C(null),_(null),N(!1),B(!1))};return _jsxs("div",{children:[_jsxs("div",{className:"p-inputgroup flex-1",children:[_jsx("span",{className:"p-inputgroup-addon",children:e}),_jsx(InputNumber,{...h,ref:D,invalid:k,min:s,max:i,minFractionDigits:n,maxFractionDigits:o,mode:a,prefix:l,suffix:r,showButtons:c,step:p,placeholder:v,value:E,onChange:e=>{return t=e.value,y||(g(E),N(!0)),void C(t);var t},locale:m,currency:u,onKeyDown:e=>{"Enter"===e.key?I():"Escape"===e.key&&w()},disabled:x}),_jsx(Button,{icon:"pi pi-check",disabled:x||!y,className:"p-button-success",onClick:()=>I(),visible:y,size:"small",autoFocus:!1}),_jsx(Button,{icon:"pi pi-times",disabled:x||!y,className:"p-button-danger",onClickCapture:()=>w(),visible:y,size:"small",autoFocus:!1})]}),d&&_jsx("small",{children:d})]})};export default ValueInput;
@@ -29,7 +29,7 @@
29
29
  * and update the `speed` and `temperature` states via `setSpeed` and `setTemperature` respectively
30
30
  * whenever new data is received.
31
31
  */
32
- export declare const useAdsRegisterSymbols: (setters: Record<string, (value: any) => void>, symbols: Record<string, string>) => null;
32
+ export declare const useAdsRegisterSymbols: (setters: Record<string, (value: any) => void>, symbols: Record<string, string>, options?: Record<string, {}>) => null;
33
33
  /**
34
34
  * Custom hook to create a function that sends a specified value to a remote device or server.
35
35
  * This hook abstracts the repetitive logic involved in sending these values,
@@ -1 +1 @@
1
- import{useContext,useRef,useEffect,useCallback}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";function sleep(e){return new Promise((t=>setTimeout(t,e)))}export const useAdsRegisterSymbols=(e,t)=>{const{invoke:n,subscribe:r,unsubscribe:s,isConnected:o}=useContext(EventEmitterContext),u=useRef([]),c=useRef(!0);return useEffect((()=>{c.current=!0;const a=async()=>{for(const[s,o]of Object.entries(t))try{await n("ADS","register_symbol",{symbol_name:o});const t=r(`ADS/${o}`,(t=>{if(c.current){const n=e[s];n&&n(t.value)}}));u.current.push(t)}catch(e){}await n("ADS","refresh",{})};if(o())a();else{let e=r("HUB/connected",(()=>{a(),s(e)}));u.current.push(e)}return()=>{c.current=!1,u.current.forEach((e=>s(e))),u.current=[]}}),[]),null};export function useAdsWriteValue(e){const{invoke:t}=useContext(EventEmitterContext);return async n=>{try{await t("ADS","write_value",{symbol_name:e,value:n})}catch(e){}}}export function useAdsWriteScaledValue(e,t,n){const{invoke:r}=useContext(EventEmitterContext);return useCallback((async s=>{const o=(s-n)/t;try{await r("ADS","write_value",{symbol_name:e,value:o})}catch(e){}}),[e,t,n,r])}export function useAdsTapValue(e){const{invoke:t}=useContext(EventEmitterContext),n="write_value";return async()=>{try{await t("ADS",n,{symbol_name:e,value:!0}),await sleep(300),await t("ADS",n,{symbol_name:e,value:!1})}catch(e){}}}
1
+ import{useContext,useRef,useEffect,useCallback}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";function sleep(e){return new Promise((t=>setTimeout(t,e)))}export const useAdsRegisterSymbols=(e,t,n={})=>{const{invoke:r,subscribe:s,unsubscribe:o,isConnected:u}=useContext(EventEmitterContext),c=useRef([]),a=useRef(!0);return useEffect((()=>{a.current=!0;const i=async()=>{for(const[o,u]of Object.entries(t))try{const t=n[o]?{symbol_name:u,options:n[o]}:{symbol_name:u};await r("ADS","register_symbol",t);const i=s(`ADS/${u}`,(t=>{if(a.current){const n=e[o];n&&n(t.value)}}));c.current.push(i)}catch(e){}await r("ADS","refresh",{})};if(u())i();else{let e=s("HUB/connected",(()=>{i(),o(e)}));c.current.push(e)}return()=>{a.current=!1,c.current.forEach((e=>o(e))),c.current=[]}}),[]),null};export function useAdsWriteValue(e){const{invoke:t}=useContext(EventEmitterContext);return async n=>{try{await t("ADS","write_value",{symbol_name:e,value:n})}catch(e){}}}export function useAdsWriteScaledValue(e,t,n){const{invoke:r}=useContext(EventEmitterContext);return useCallback((async s=>{const o=(s-n)/t;try{await r("ADS","write_value",{symbol_name:e,value:o})}catch(e){}}),[e,t,n,r])}export function useAdsTapValue(e){const{invoke:t}=useContext(EventEmitterContext),n="write_value";return async()=>{try{await t("ADS",n,{symbol_name:e,value:!0}),await sleep(300),await t("ADS",n,{symbol_name:e,value:!1})}catch(e){}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.0.22",
3
+ "version": "3.0.25",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -2,17 +2,17 @@
2
2
  * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
3
  * Created Date: 2024-03-15 13:21:06
4
4
  * -----
5
- * Last Modified: 2024-03-18 11:31:36
5
+ * Last Modified: 2024-05-20 08:38:24
6
6
  * -----
7
7
  *
8
8
  */
9
9
 
10
+ import React, { forwardRef, useImperativeHandle, useCallback, useState } from 'react';
10
11
  import Blockly from "blockly";
11
12
  import { pythonGenerator } from 'blockly/python';
12
13
 
13
14
  import { BlocklyWorkspace } from 'react-blockly';
14
15
 
15
- import React from 'react';
16
16
 
17
17
  import "./BlocklyEditor.css";
18
18
 
@@ -25,7 +25,7 @@ interface BlocklyEditorProps {
25
25
  * The initial XML representation of the Blockly workspace. Can be undefined
26
26
  * if the workspace should start empty.
27
27
  */
28
- initialXml: string | undefined;
28
+ initialXml?: string | undefined;
29
29
 
30
30
  /**
31
31
  * The configuration definition for the Blockly Toolbox.
@@ -52,7 +52,7 @@ interface BlocklyEditorProps {
52
52
  * Callback fired when the Blockly workspace content changes.
53
53
  * Note: This does not directly reflect changes to the XML or the code.
54
54
  */
55
- onWorkspaceChanged?: () => void;
55
+ onWorkspaceChanged?: (workspace: Blockly.WorkspaceSvg | undefined) => void;
56
56
 
57
57
  /**
58
58
  * Callback fired when the XML representation of the workspace changes.
@@ -66,42 +66,12 @@ interface BlocklyEditorProps {
66
66
  */
67
67
  onCodeChanged?: (code: string) => void;
68
68
 
69
- }
70
-
71
-
72
- /**
73
- * State of the BlocklyEditor
74
- */
75
- interface BlocklyEditorState {
76
-
77
- /**
78
- * A name for the Blockly editor, potentially used for identification.
79
- */
80
- name: string;
81
-
82
- /**
83
- * The current XML representation of the workspace.
84
- */
85
- xml: string;
86
-
87
- /**
88
- * The current generated code (e.g., Python).
89
- */
90
- code: string;
91
-
92
- /**
93
- * The height of the first parent container with
94
- * classname "blockly-container". It is important to set
95
- * a parent container with this class name for auto-sizing to work!
96
- */
97
- parentHeight: number;
98
69
 
99
70
  /**
100
- * The width of the first parent container with
101
- * classname "blockly-container". It is important to set
102
- * a parent container with this class name for auto-sizing to work!
71
+ * Callback fired when an error occurs.
72
+ * Includes an error message.
103
73
  */
104
- parentWidth: number;
74
+ onError? : (message : string) => void;
105
75
 
106
76
  }
107
77
 
@@ -118,62 +88,107 @@ interface BlocklyEditorState {
118
88
  *
119
89
  * Right now, the editor only supports generating python code, but a feature to specify the
120
90
  * generator for different languages is planned.
91
+ *
92
+ * ## Usage Example
93
+ *
94
+ * ```tsx
95
+ * import React, { useRef } from 'react';
96
+ * import BlocklyEditor from './BlocklyEditor';
97
+ * import { StandardToolbox } from './toolbox';
98
+ *
99
+ * const App: React.FC = () => {
100
+ * const blocklyEditorRef = useRef<{ newBlockly: () => void; loadBlockly: (xmlString: string) => void }>(null);
101
+ *
102
+ * const handleNew = () => {
103
+ * if (blocklyEditorRef.current) {
104
+ * blocklyEditorRef.current.newBlockly();
105
+ * }
106
+ * };
107
+ *
108
+ * const handleLoad = (xmlString: string) => {
109
+ * if (blocklyEditorRef.current) {
110
+ * blocklyEditorRef.current.loadBlockly(xmlString);
111
+ * }
112
+ * };
113
+ *
114
+ * return (
115
+ * <div className="blockly-container" style={{ height: '500px' }}>
116
+ * <BlocklyEditor
117
+ * ref={blocklyEditorRef}
118
+ * initialXml="<xml xmlns='http://www.w3.org/1999/xhtml'></xml>"
119
+ * toolbox={StandardToolbox}
120
+ * widthAdjustment={0}
121
+ * heightAdjustment={0}
122
+ * onWorkspaceChanged={(workspace) => console.log('Workspace changed:', workspace)}
123
+ * onXmlChanged={(xml) => console.log('XML changed:', xml)}
124
+ * onCodeChanged={(code) => console.log('Code changed:', code)}
125
+ * onError={(message) => console.error('Error:', message)}
126
+ * />
127
+ * <button onClick={handleNew}>New</button>
128
+ * <button onClick={() => handleLoad('<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_if" x="10" y="10"></block></xml>')}>Load</button>
129
+ * </div>
130
+ * );
131
+ * };
132
+ *
133
+ * export default App;
134
+ * ```
121
135
  */
122
- export class BlocklyEditor extends React.Component<BlocklyEditorProps, BlocklyEditorState> {
123
-
124
- static defaultProps = {
125
- initialXml: undefined,
126
- widthAdjustment: 0,
127
- heightAdjustment: 0,
128
- onWorkspaceChanged: undefined,
129
- onXmlChanged : undefined,
130
- onCodeChanged : undefined
131
- };
132
-
133
- // Explicitly type the ref as a reference to a HTMLDivElement
134
- private ref = React.createRef<HTMLDivElement>();
136
+ export const BlocklyEditor = forwardRef((props: BlocklyEditorProps, ref) => {
137
+
138
+ const [_xml, setXml] = useState<string>("");
139
+ const [_code, setCode] = useState<string>("");
140
+ const [parentWidth, setParentWidth] = useState<number>(0);
141
+ const [parentHeight, setParentHeight] = useState<number>(0);
142
+ const [editorWorkspace, setEditorWorkspace] = useState<Blockly.WorkspaceSvg | undefined>(undefined);
143
+
144
+ const containerRef = React.createRef<HTMLDivElement>();
145
+
146
+ useImperativeHandle(ref, () => ({
147
+ /**
148
+ * Clears the Blockly workspace, removing all blocks.
149
+ */
150
+ newBlockly() {
151
+ if (editorWorkspace) {
152
+ editorWorkspace.clear();
153
+ }
154
+ },
155
+ /**
156
+ * Loads the given XML string into the Blockly workspace.
157
+ * @param xmlString - The XML string representing the Blockly workspace to be loaded.
158
+ */
159
+ loadBlockly(xmlString: string) {
160
+ if (editorWorkspace) {
161
+ try {
162
+ editorWorkspace.clear();
163
+ const xml = Blockly.utils.xml.textToDom(xmlString);
164
+ Blockly.Xml.domToWorkspace(xml, editorWorkspace);
165
+ } catch (err) {
166
+ if (props.onError) {
167
+ props.onError(`${err}`);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }));
135
173
 
136
- /**
137
- * Constructor
138
- */
139
- constructor(props: BlocklyEditorProps) {
140
- super(props);
141
- this.state = {
142
- name: '',
143
- xml: '',
144
- code: '',
145
- parentWidth: 0,
146
- parentHeight: 0
174
+ React.useEffect(() => {
175
+ updateParentSize();
176
+ window.addEventListener('resize', updateParentSize);
177
+ return () => {
178
+ window.removeEventListener('resize', updateParentSize);
147
179
  };
180
+ }, []);
148
181
 
149
- }
150
-
151
- componentDidMount() {
152
- this.updateParentSize();
153
- window.addEventListener('resize', this.updateParentSize);
154
- }
155
-
156
- componentDidUpdate(prevProps: BlocklyEditorProps) {
157
- prevProps;
158
- // Logic to handle updates in props, if necessary
159
- }
160
-
161
- componentWillUnmount() {
162
- window.removeEventListener('resize', this.updateParentSize);
163
- }
164
-
165
-
166
- updateParentSize = () => {
167
-
168
- if (this.ref.current) {
169
-
170
-
171
- const parent = this.ref.current.parentElement;
182
+ /**
183
+ * Updates the size of the parent container to ensure the Blockly workspace fits within it.
184
+ */
185
+ const updateParentSize = useCallback(() => {
186
+ if (containerRef.current) {
172
187
  let w = 0;
173
188
  let h = 0;
174
- let p = parent;
189
+ let p = containerRef.current.parentElement;
175
190
 
176
- while (p !== undefined && p !== null) {
191
+ while (p !== null) {
177
192
  w = p.offsetWidth;
178
193
  h = p.offsetHeight;
179
194
 
@@ -184,72 +199,68 @@ export class BlocklyEditor extends React.Component<BlocklyEditorProps, BlocklyEd
184
199
  p = p.parentElement;
185
200
  }
186
201
 
187
- if (w < 300) {
188
- w = 300;
189
- }
190
-
191
- if (h < 300)
192
- h = 300;
202
+ if (w < 300) w = 300;
203
+ if (h < 300) h = 300;
193
204
 
194
- this.setState({
195
- parentWidth: (w * 0.9) + this.props.widthAdjustment,
196
- parentHeight: (h * 0.9) + this.props.heightAdjustment,
197
- });
205
+ setParentWidth((w * 0.9) + props.widthAdjustment);
206
+ setParentHeight((h * 0.9) + props.heightAdjustment);
198
207
  }
199
- };
200
-
208
+ }, [props.widthAdjustment, props.heightAdjustment]);
201
209
 
202
-
203
-
204
- private onWorkspaceChanged = (workspace: Blockly.WorkspaceSvg | undefined) => {
210
+ /**
211
+ * Callback function that is triggered when the Blockly workspace changes.
212
+ * It updates the generated code and triggers the appropriate callback.
213
+ * @param workspace - The Blockly workspace that has changed.
214
+ */
215
+ const onWorkspaceChanged = (workspace: Blockly.WorkspaceSvg | undefined) => {
205
216
  const code = pythonGenerator.workspaceToCode(workspace);
206
- this.setState({ code: code });
207
- if (this.props.onWorkspaceChanged !== undefined && this.props.onWorkspaceChanged !== null)
208
- this.props.onWorkspaceChanged();
209
-
210
- if (this.props.onCodeChanged !== undefined && this.props.onCodeChanged !== null)
211
- this.props.onCodeChanged(code);
212
- }
213
-
214
- private setXml = (xml: string) => {
215
- this.setState({ xml: xml });
216
-
217
- if (this.props.onXmlChanged !== undefined && this.props.onXmlChanged !== null)
218
- this.props.onXmlChanged(xml);
219
- }
220
-
221
- private onInject = (workspace: Blockly.WorkspaceSvg) => {
222
- workspace;
223
- }
217
+ setCode(code);
218
+ if (props.onWorkspaceChanged) props.onWorkspaceChanged(workspace);
219
+ if (props.onCodeChanged) props.onCodeChanged(code);
220
+ };
224
221
 
225
222
 
226
- render() {
227
- return (
228
- <>
229
- <BlocklyWorkspace
230
- className="fill-height" // you can use whatever classes are appropriate for your app's CSS
231
- toolboxConfiguration={this.props.toolbox} // this must be a JSON toolbox definition
232
- initialXml={this.props.initialXml}
233
- onWorkspaceChange={this.onWorkspaceChanged}
234
- onXmlChange={this.setXml}
235
- onInject={this.onInject}
236
- workspaceConfiguration={{
237
- grid: {
238
- spacing: 20,
239
- length: 3,
240
- colour: "#ccc",
241
- snap: true,
242
- },
243
- }}
244
- />
245
-
246
- </>
247
-
248
- )
223
+ /**
224
+ * Callback function that is triggered when the XML representation of the workspace changes.
225
+ * @param s - The updated XML string.
226
+ */
227
+ const onXmlChanged = (s : string) => {
228
+ setXml(s);
229
+ if (props.onXmlChanged !== undefined && props.onXmlChanged !== null) {
230
+ props.onXmlChanged(s);
231
+ }
249
232
  }
250
233
 
234
+ /**
235
+ * Callback function that is triggered when the Blockly workspace is injected.
236
+ * It sets the workspace reference.
237
+ * @param workspace - The Blockly workspace that has been injected.
238
+ */
239
+ const onInject = (workspace: Blockly.WorkspaceSvg) => {
240
+ setEditorWorkspace(workspace);
241
+ };
251
242
 
252
- }
243
+ return (
244
+ <div ref={containerRef} style={{ width: parentWidth, height: parentHeight }}>
245
+ <BlocklyWorkspace
246
+ className="fill-height"
247
+ toolboxConfiguration={props.toolbox}
248
+ initialXml={props.initialXml}
249
+ onWorkspaceChange={onWorkspaceChanged}
250
+ onXmlChange={onXmlChanged}
251
+ onInject={onInject}
252
+ workspaceConfiguration={{
253
+ grid: {
254
+ spacing: 20,
255
+ length: 3,
256
+ colour: "#ccc",
257
+ snap: true,
258
+ },
259
+ }}
260
+ />
261
+ </div>
262
+ );
263
+ });
253
264
 
254
265
  export default BlocklyEditor;
255
266
 
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
3
  * Created Date: 2024-03-20 13:05:42
4
4
  * -----
5
- * Last Modified: 2024-04-27 17:33:46
5
+ * Last Modified: 2024-05-17 10:04:53
6
6
  * -----
7
7
  *
8
8
  */
@@ -10,7 +10,7 @@
10
10
 
11
11
  import React from "react";
12
12
 
13
- import { InputText } from 'primereact/inputtext';
13
+ import { InputText, InputTextProps } from 'primereact/inputtext';
14
14
  import { Button } from "primereact/button";
15
15
 
16
16
  import { EventEmitterContext } from "../core/EventEmitterContext.js";
@@ -19,7 +19,7 @@ import { KeyFilterType } from "primereact/keyfilter";
19
19
  /**
20
20
  * Properties of the component.
21
21
  */
22
- interface TextInputProps {
22
+ interface TextInputProps extends Omit<InputTextProps, "prefix"> {
23
23
 
24
24
  /**
25
25
  * The label for the field.
@@ -211,6 +211,8 @@ export class TextInput extends React.Component<TextInputProps, TextInputState> {
211
211
 
212
212
  render() {
213
213
 
214
+ const { prefix, ...restProps} = this.props;
215
+
214
216
  return(
215
217
  <div>
216
218
  <div className="p-inputgroup flex-1" >
@@ -223,6 +225,7 @@ export class TextInput extends React.Component<TextInputProps, TextInputState> {
223
225
  </span>
224
226
  }
225
227
  <InputText
228
+ {...restProps}
226
229
  keyfilter={this.props.keyFilter}
227
230
  placeholder={this.props.placeholder}
228
231
  value={this.state.entryValue}
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
3
  * Created Date: 2024-03-20 13:05:42
4
4
  * -----
5
- * Last Modified: 2024-04-30 08:33:37
5
+ * Last Modified: 2024-05-17 10:01:45
6
6
  * -----
7
7
  *
8
8
  */
@@ -58,14 +58,14 @@
58
58
 
59
59
 
60
60
  import React, { useState, useRef, useEffect, useContext } from 'react';
61
- import { InputNumber } from 'primereact/inputnumber';
61
+ import { InputNumber, InputNumberProps } from 'primereact/inputnumber';
62
62
  import { EventEmitterContext } from "../core/EventEmitterContext";
63
63
  import { Button } from 'primereact/button';
64
64
 
65
65
  /**
66
66
  * Properties of the ValueInput component.
67
67
  */
68
- interface ValueInputProps {
68
+ interface ValueInputProps extends Omit<InputNumberProps, 'value'> {
69
69
 
70
70
  /**
71
71
  * The label for the ValueInput field.
@@ -209,7 +209,8 @@ export const ValueInput: React.FC<ValueInputProps> = ({
209
209
  disabled = false,
210
210
  dispatchTopic = undefined,
211
211
  placeholder = undefined,
212
- onValueChanged = undefined
212
+ onValueChanged = undefined,
213
+ ...restProps
213
214
  }) => {
214
215
  const [entryValue, setEntryValue] = useState<number | null>(value);
215
216
  const [currentValue, setCurrentValue] = useState<number | null>(value);
@@ -311,6 +312,7 @@ export const ValueInput: React.FC<ValueInputProps> = ({
311
312
  <div className="p-inputgroup flex-1">
312
313
  <span className="p-inputgroup-addon">{label}</span>
313
314
  <InputNumber
315
+ {...restProps}
314
316
  ref={inputRef}
315
317
  invalid={invalidValue}
316
318
  min={min}
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
3
  * Created Date: 2024-04-26 09:04:40
4
4
  * -----
5
- * Last Modified: 2024-04-30 21:32:01
5
+ * Last Modified: 2024-05-17 10:47:22
6
6
  * -----
7
7
  *
8
8
  */
@@ -55,7 +55,11 @@ function sleep(ms: number): Promise<void> {
55
55
  * and update the `speed` and `temperature` states via `setSpeed` and `setTemperature` respectively
56
56
  * whenever new data is received.
57
57
  */
58
- export const useAdsRegisterSymbols = (setters: Record<string, (value: any) => void>, symbols: Record<string, string>) => {
58
+ export const useAdsRegisterSymbols = (
59
+ setters: Record<string, (value: any) => void>,
60
+ symbols: Record<string, string>,
61
+ options: Record<string, {}> = {},
62
+ ) => {
59
63
  const { invoke, subscribe, unsubscribe, isConnected } = useContext(EventEmitterContext);
60
64
  const subscriptions = useRef<number[]>([]);
61
65
  const isMounted = useRef(true);
@@ -66,7 +70,9 @@ export const useAdsRegisterSymbols = (setters: Record<string, (value: any) => vo
66
70
  const registerAndSubscribe = async () => {
67
71
  for (const [key, symbolName] of Object.entries(symbols)) {
68
72
  try {
69
- await invoke("ADS", "register_symbol", { symbol_name: symbolName });
73
+ const invokeOptions = options[key] ? { symbol_name: symbolName, options : options[key] } : { symbol_name: symbolName };
74
+ await invoke("ADS", "register_symbol", invokeOptions);
75
+
70
76
  //console.log(`Subscribe... ADS/${symbolName}`);
71
77
  const subscriptionId = subscribe(`ADS/${symbolName}`, (data) => {
72
78
  if (isMounted.current) {