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
Store flow node instance in CustomNodeModel config
  • Loading branch information
JoshuaCWebDeveloper committed Apr 18, 2024
commit 40773c35523d951c0bec4081325a7735c1e65b9d
Original file line number Diff line number Diff line change
@@ -230,9 +230,18 @@ export const FlowCanvas: React.FC<FlowCanvasProps> = ({
return;
}

const node = new CustomNodeModel(entity, {
const config = nodeLogic.applyConfigDefaults(
{} as FlowNodeEntity,
entity
);

const node = new CustomNodeModel({
name: entity.type,
color: entity.color,
extras: {
entity,
config,
},
});

node.setPosition(nodePosition);
29 changes: 18 additions & 11 deletions packages/flow-client/src/app/components/flow-canvas/node.tsx
Original file line number Diff line number Diff line change
@@ -138,8 +138,10 @@ export const Node: React.FC<NodeProps> = ({ node, engine }) => {
const entity = node.entity ?? ({} as NodeEntity);

return (
<StyledNode className={node.isSelected() ? 'selected' : ''}>
<NodeRedNode node={node.entity}>
<StyledNode
className={node.isSelected() ? 'selected' : ''}
>
<NodeRedNode entity={entity} instance={node.config}>
{/* Render ports */}

{ports.map((port, index) => (
@@ -162,15 +164,23 @@ export const Node: React.FC<NodeProps> = ({ node, engine }) => {

// Assuming createCustomNodeModel exists, and you're adding to this file
export class CustomNodeModel extends DefaultNodeModel {
constructor(public entity: NodeEntity, options?: Record<string, unknown>) {
public entity?: NodeEntity;
public config?: FlowNodeEntity;

constructor(options: {
extras: {
entity: NodeEntity;
config: FlowNodeEntity;
[index: string]: unknown;
};
[index: string]: unknown;
}) {
super({
...options,
extras: {
...(options?.extras ?? {}),
entity: entity,
},
type: 'custom-node',
});
this.entity = options?.extras?.entity;
this.config = options?.extras?.config;
}
}

@@ -190,9 +200,6 @@ export class CustomNodeFactory extends AbstractReactFactory<
}

generateModel(_event: GenerateModelEvent) {
return new CustomNodeModel(
_event.initialConfig.extras.entity,
_event.initialConfig
);
return new CustomNodeModel(_event.initialConfig);
}
}
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ const StyledNodeItem = styled.li<{ node: NodeEntity }>`
export const Node: React.FC<NodeProps> = ({ node }) => {
return (
<StyledNodeItem node={node} className="node-item">
<NodeRedNode node={node} />
<NodeRedNode entity={node} />
</StyledNodeItem>
);
};
20 changes: 13 additions & 7 deletions packages/flow-client/src/app/components/node/node-red-node.tsx
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import styled from 'styled-components';

import { NodeEntity } from '../../redux/modules/node/node.slice';
import environment from '../../../environment';
import { FlowNodeEntity } from '../../redux/modules/flow/flow.slice';

// Styled component for the node item
const StyledNode = styled.div<{ node: NodeEntity }>`
@@ -16,7 +17,7 @@ const StyledNode = styled.div<{ node: NodeEntity }>`
position: relative;
height: 35px;

.type {
.name {
flex: 1;
text-align: ${props => props.node.align || 'left'};
}
@@ -29,21 +30,26 @@ const StyledNode = styled.div<{ node: NodeEntity }>`
`;

export type NodeRedNodeProps = {
node: NodeEntity;
entity: NodeEntity;
instance?: FlowNodeEntity;
children?: React.ReactNode;
};

export const NodeRedNode = ({ node, children }: NodeRedNodeProps) => {
export const NodeRedNode = ({
entity,
instance,
children,
}: NodeRedNodeProps) => {
return (
<StyledNode node={node} className="node node-red">
{node.icon && (
<StyledNode node={entity} className="node node-red">
{entity.icon && (
<img
className="icon"
src={`${environment.NODE_RED_API_ROOT}/icons/node-red/${node.icon}`}
src={`${environment.NODE_RED_API_ROOT}/icons/node-red/${entity.icon}`}
alt="Node Icon"
/>
)}
<span className="type">{node.type}</span>
<span className="name">{instance?.name || entity.type}</span>
{children}
</StyledNode>
);
38 changes: 38 additions & 0 deletions packages/flow-client/src/app/redux/modules/node/node.logic.ts
Original file line number Diff line number Diff line change
@@ -256,4 +256,42 @@ export class NodeLogic {

return { inputs, outputs };
}

// Helper method to extract default values from NodeDefaults
private extractDefaultNodeValues(
defaults: NonNullable<NodeEntity['defaults']>
) {
const config: Record<string, unknown> = {};
Object.keys(defaults).forEach(key => {
const property = defaults[key];
if (
property &&
'value' in property &&
property.value !== '_DEFAULT_'
) {
config[key] = property.value;
}
});
return config;
}
// Method to generate a default config for a DiagramNode based on its NodeEntity
public applyConfigDefaults(
node: FlowNodeEntity,
entity: NodeEntity
): FlowNodeEntity {
// Generate default config based on the NodeEntity's defaults
// Now we need to extract the value from each DefaultProperty
const defaultConfig = entity.defaults
? this.extractDefaultNodeValues(entity.defaults)
: {};

// Apply the default config to the existing config without overriding existing values
const updatedNode = {
name: '',
...defaultConfig,
...node,
};

return updatedNode;
}
}
55 changes: 34 additions & 21 deletions packages/flow-client/src/app/redux/modules/node/node.slice.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,25 @@ import {

export const NODE_FEATURE_KEY = 'node';

export interface NodeEntity {
type SerializedFunction = {
type: 'serialized-function';
value: string;
};

// Define the structure of a default property
type DefaultProperty<T> = {
value: T;
required?: boolean;
validate?: SerializedFunction;
};

// Define the structure of the defaults object more precisely
type NodeDefaults = {
// Add more properties as needed
[key: string]: DefaultProperty<unknown>;
};

export type NodeEntity = {
// Core properties
id: string;
nodeRedId: string;
@@ -17,7 +35,7 @@ export interface NodeEntity {
category?: string;
module: string;
version: string;
defaults?: Record<string, unknown>; // Object with editable properties for the node
defaults?: NodeDefaults; // Object with editable properties for the node
inputs?: number;
outputs?: number;
icon?: string;
@@ -26,37 +44,33 @@ export interface NodeEntity {
color?: string;
align?: string;
paletteLabel?: string;
inputLabels?: {
type: 'serialized-function';
value: string;
};
outputLabels?: {
type: 'serialized-function';
value: string;
};
inputLabels?: SerializedFunction;
outputLabels?: SerializedFunction;
label?: string;
labelStyle?: string;
editorTemplate?: string;
helpTemplate?: string;

// Event handlers
oneditprepare?: string;
oneditsave?: string;
oneditcancel?: string;
oneditdelete?: string;
oneditresize?: string;
onpaletteadd?: () => void;
onpaletteremove?: () => void;
oneditprepare?: SerializedFunction;
oneditsave?: SerializedFunction;
oneditcancel?: SerializedFunction;
oneditdelete?: SerializedFunction;
oneditresize?: SerializedFunction;
onpaletteadd?: SerializedFunction;
onpaletteremove?: SerializedFunction;

// Other properties
enabled?: boolean;
local?: boolean;
user?: boolean;
button?: {
onclick?: () => void;
onclick?: SerializedFunction;
};
credentials?: Record<string, unknown>; // Optional object defining credential fields
}

definitionScript?: string;
};

export interface NodeState extends EntityState<NodeEntity, string> {
loadingStatus: 'not loaded' | 'loading' | 'loaded' | 'error';
@@ -113,8 +127,7 @@ export const getNodeState = (rootState: {
}): NodeState => rootState[NODE_FEATURE_KEY];
export const selectAllNodes = createSelector(getNodeState, selectAll);
export const selectNodeById = createSelector(
getNodeState,
(state: NodeState, nodeId: string) => nodeId,
[getNodeState, (state, nodeId: string) => nodeId],
(state, nodeId) => selectById(state, nodeId)
);