Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FB-05 Node Editor UI - Working w/ function node #14

Merged
merged 19 commits into from
Apr 19, 2024
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b9ea0a3
Begin FB-05
JoshuaCWebDeveloper Apr 12, 2024
b0ce91f
Merge branch 'master' into fb-05-node-editor-ui
JoshuaCWebDeveloper Apr 12, 2024
91c5156
Move overriden methods first in engine.ts
JoshuaCWebDeveloper Apr 12, 2024
ac3c669
Move node drop calculations from flow-canvas to engine
JoshuaCWebDeveloper Apr 12, 2024
95d0cb1
Make connection between react-diagram and flow.slice state two-way
JoshuaCWebDeveloper Apr 17, 2024
6233e75
Finish implementing two-way state with flow-canvas and apply fixes
JoshuaCWebDeveloper Apr 18, 2024
f9f4447
Install and configure redux-persist for flow slice
JoshuaCWebDeveloper Apr 18, 2024
d66792f
Fix current errors in PR
JoshuaCWebDeveloper Apr 18, 2024
d6df467
Correct redux-persist import
JoshuaCWebDeveloper Apr 18, 2024
2eef906
Mock and copy various modules from the Node-RED client for node editing
JoshuaCWebDeveloper Apr 18, 2024
8581d64
Add dom-iterable ts lib
JoshuaCWebDeveloper Apr 18, 2024
52a3451
Import font-awesome
JoshuaCWebDeveloper Apr 18, 2024
40773c3
Store flow node instance in CustomNodeModel config
JoshuaCWebDeveloper Apr 18, 2024
3d414e4
Move Node-RED logic from modules/node to red/
JoshuaCWebDeveloper Apr 18, 2024
b8ea876
New <NodeEditor /> and builder slice for editing nodes
JoshuaCWebDeveloper Apr 18, 2024
1102b92
Write tests for builder
JoshuaCWebDeveloper Apr 18, 2024
e945b11
Fix existing tests
JoshuaCWebDeveloper Apr 19, 2024
148fa36
Write additional tests for flow.logic
JoshuaCWebDeveloper Apr 19, 2024
78186aa
Write additional tests for node redux module
JoshuaCWebDeveloper Apr 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Mock and copy various modules from the Node-RED client for node editing
  • Loading branch information
JoshuaCWebDeveloper committed Apr 18, 2024
commit 2eef9060bc8b5eecad96ef5ff6889f6eeea7aed8
87 changes: 83 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -11,11 +11,16 @@
"type": "module",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
"@projectstorm/react-canvas-core": "^7.0.3",
"@projectstorm/react-diagrams": "^7.0.4",
"@reduxjs/toolkit": "^2.2.3",
"dompurify": "^3.1.0",
"i18next": "^23.11.2",
"jquery-i18next": "^1.2.1",
"jsonata": "^2.0.4",
"marked": "^12.0.1",
"monaco-editor": "=0.47.0",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
195 changes: 195 additions & 0 deletions packages/flow-client/src/app/red/execute-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { NodeEntity } from '../redux/modules/node/node.slice';
import { Context } from './mock-jquery';
import { createMockRed } from './mock-red';

const executeDefinitionScript = (
definitionScript: string,
RED: ReturnType<typeof createMockRed>
) => {
// eslint-disable-next-line no-new-func
const scriptFunction = new Function('RED', '$', definitionScript);

try {
// Call the script function with the RED object
scriptFunction(RED, RED.$);
} catch (error) {
console.error('Error executing script:', error);
}
};

export const createNodeInstance = (nodeConfig: Record<string, unknown>) => {
const RED = createMockRed();
const nodeInstance = new Proxy(
{
...nodeConfig,
_(messagePath: string) {
return RED._(`node-red:${messagePath}`);
},
},
// proxy handler
{
get: function (target, prop) {
if (prop in target) {
return target[prop as keyof typeof target];
} else {
console.error(
`Attempted to access Node instance property: \`${String(
prop
)}\` but it was not emulated.`
);
return undefined;
}
},
}
);

return nodeInstance;
};

// Utility function to deserialize a function from its serialized string representation
export const deserializeFunction = <T = (...args: unknown[]) => unknown>(
serializedFunction: string,
nodeConfig: Record<string, unknown>,
context = createNodeInstance(nodeConfig)
): T => {
const nodeInstance = context;
try {
console.debug('Deserializing function: ');
console.debug(serializedFunction);
// eslint-disable-next-line no-new-func
return new Function(
'nodeInstance',
`return (${serializedFunction}).bind(nodeInstance);`
)(nodeInstance);
} catch (error) {
console.error('Error deserializing function: ');
console.info(serializedFunction);
throw error;
}
};

export const executeRegisterType = (definitionScript: string) => {
let registeredType = null;

const RED = createMockRed();
RED.nodes.registerType = (
type: string,
definition: Partial<NodeEntity>
) => {
// recursively iterate through definition and serialize any functions
const serializeFunctions = (obj: Record<string, unknown>) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'function') {
obj[key] = {
type: 'serialized-function',
value: (
obj[key] as (...args: unknown[]) => unknown
).toString(),
};
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
serializeFunctions(obj[key] as Record<string, unknown>);
}
});
};
serializeFunctions(definition);
registeredType = {
type,
definition: definition,
};
};

executeDefinitionScript(definitionScript, RED);

return registeredType as {
type: string;
definition: Partial<NodeEntity>;
} | null;
};

export const extractNodePropertyFn = <T = (...args: unknown[]) => unknown>(
definitionScript: string,
propertyPath: string,
rootContext: Context = window.document,
nodeConfig: Record<string, unknown> = {},
context = createNodeInstance(nodeConfig)
) => {
let propertyFn = null;

const RED = createMockRed(rootContext);
const nodeInstance = context;

RED.nodes.registerType = (
type: string,
definition: Partial<NodeEntity>
) => {
const getPropertyByPath = (
obj: Record<string, unknown>,
path: string
) => {
return path
.split('.')
.reduce(
(acc, part) =>
acc && (acc[part] as Record<string, unknown>),
obj
);
};

const propertyFunction = getPropertyByPath(definition, propertyPath);
if (typeof propertyFunction === 'function') {
propertyFn = (
propertyFunction as (...args: unknown[]) => unknown
).bind(nodeInstance) as unknown as T;
}
};

executeDefinitionScript(definitionScript, RED);

return propertyFn as T | null;
};

export const finalizeNodeExecution = (
dialogForm: HTMLElement,
rootContext: Context = window.document
) => {
// apply monaco styles to shadow dom
const linkElement = document.createElement('link');
linkElement.setAttribute('rel', 'stylesheet');
linkElement.setAttribute('type', 'text/css');
linkElement.setAttribute('data-name', 'vs/editor/editor.main');
linkElement.setAttribute(
'href',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.47.0/dev/vs/editor/editor.main.css'
);

rootContext.appendChild(linkElement);

// apply node-red namespace to i18n keys
Array.from(dialogForm.querySelectorAll<HTMLElement>('[data-i18n]')).forEach(
element => {
const currentKeys = element.dataset.i18n as string;
const newKeys = currentKeys
.split(';')
.map(key => {
if (key.includes(':')) {
return key;
}

let prefix = '';
if (key.startsWith('[')) {
const parts = key.split(']');
prefix = parts[0] + ']';
key = parts[1];
}

return `${prefix}node-red:${key}`;
})
.join(';');
element.dataset.i18n = newKeys;
}
);

// call i18n plugin on newly created content
const RED = createMockRed(rootContext);
(RED.$(dialogForm) as unknown as { i18n: () => void }).i18n();
};
Loading