Skip to content

Commit 5266c66

Browse files
committed
Final version updates
1 parent 981b632 commit 5266c66

File tree

9 files changed

+214
-12
lines changed

9 files changed

+214
-12
lines changed

server/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import os
2+
import logging
3+
import traceback
24

35
from server.db.models import session
46

57

68
# Constants
79
VERSION = "0.0.1"
10+
logger = logging.getLogger("control")
811

912
# Middleware
1013
def response_handler(func):
@@ -18,7 +21,8 @@ def wrapper(*args, **kwargs):
1821
return data
1922
except Exception as error:
2023
# Print error in red
21-
print("\033[91m" + str(error) + "\033[0m")
24+
logger.error("\033[91m" + str(error) + "\033[0m")
25+
logger.error(traceback.format_exc())
2226

2327
# Rollback database session
2428
try:

server/db/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class Flow(Base):
5656
nodes = relationship('Node', back_populates='flow', cascade='all, delete, delete-orphan')
5757
edges = relationship('Edge', back_populates='flow', cascade='all, delete, delete-orphan')
5858

59+
sql.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'])
60+
5961
@validates('name')
6062
def validate_name(self, key, name):
6163
assert len(name) > 0
@@ -125,6 +127,8 @@ class Node(Base):
125127
node_type_id = sql.Column(sql.Integer, sql.ForeignKey('node_types.id'))
126128
node_type = relationship('NodeType', back_populates='nodes')
127129

130+
sql.ForeignKeyConstraint(['workspace_id', 'flow_id', 'node_type_id',], ['workspaces.id', 'flows.id', 'node_types.id'])
131+
128132
@validates('name')
129133
def validate_name(self, key, name):
130134
assert len(name) > 0
@@ -156,6 +160,8 @@ class Edge(Base):
156160
edge_type_id = sql.Column(sql.Integer, sql.ForeignKey('edge_types.id'))
157161
edge_type = relationship('EdgeType', back_populates='edges')
158162

163+
sql.ForeignKeyConstraint(['workspace_id', 'flow_id', 'edge_type_id',], ['workspaces.id', 'flows.id', 'edge_types.id'])
164+
159165
@validates('name')
160166
def validate_name(self, key, name):
161167
assert len(name) > 0

server/resolvers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def updateNode(_, info, **kwargs):
152152

153153
if node_data:= kwargs.pop('node', None):
154154
node_data = loads(unquote(node_data))
155-
none_data.pop('selected', None)
155+
node_data.pop('selected', None)
156156
kwargs['node'] = dumps({**loads(node.node), **node_data})
157157

158158
for key, value in kwargs.items():

src/custom/input.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ function Input({ data, selected }: { data: any, selected: any }) {
1414
/>
1515
<div className="source-node">
1616
{data.label}
17-
<Handle type="source" position={Position.Bottom} id="a" />
1817
<Handle type="target" position={Position.Bottom} id="b" />
19-
<Handle type="source" position={Position.Top} id="c" />
2018
<Handle type="target" position={Position.Top} id="d" />
19+
<Handle type="source" position={Position.Bottom} id="a" />
20+
<Handle type="source" position={Position.Top} id="c" />
2121
</div>
2222
</>
2323
);

src/custom/output.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ function Output({ data, selected }: { data: any, selected: any }) {
1515
<div className="source-node">
1616
{data.label}
1717
<Handle type="target" position={Position.Top} id="a" />
18-
<Handle type="source" position={Position.Top} id="b" />
1918
<Handle type="target" position={Position.Bottom} id="c" />
19+
<Handle type="source" position={Position.Top} id="b" />
2020
<Handle type="source" position={Position.Bottom} id="d" />
2121
</div>
2222
</>

src/designer/designer.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,7 @@ hr {
147147
#root > div > div > div.react-flow__panel.top.right > div > div > div > div > div > button {
148148
font-size: 12px;
149149
}
150+
#root > div > div > div.react-flow__renderer > div > div > svg:first-child {
151+
z-index: 1002 !important;
152+
position: relative;
153+
}

src/designer/designer.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import CardNode from "../custom/card";
3636
import Source from "../custom/source";
3737
import Output from "../custom/output";
3838
import Default from "../custom/default";
39+
import SubNodeModal from "./sub_node_modal";
3940
import SubFlowNode from "../custom/subflow";
41+
import GroupCardNode from "../custom/group_card";
4042
import { ViewportChangeLogger } from "../home/viewport_func";
4143

4244
import type { EdgeTypes } from "reactflow";
@@ -48,7 +50,7 @@ let initialEdges: any[] = [];
4850

4951
const nodeTypes: {} = {
5052
// Custom node types here!
51-
source: Source, card: CardNode, output: Output, input: Input, default: Default, subflow: SubFlowNode
53+
source: Source, card: CardNode, output: Output, input: Input, default: Default, subflow: SubFlowNode, group_card: GroupCardNode
5254
};
5355
const edgeTypes: {} = {
5456
// default: DefaultEdge
@@ -77,6 +79,7 @@ export default function Designer({ activeWorkspace, setActiveWorkspace, flow }:
7779
const [showEditModal, setShowEditModal] = useState(false);
7880
const [selectedNode, setSelectedNode] = useState<any>(null);
7981
const [selectedEdge, setSelectedEdge] = useState<any>(null);
82+
const [showSubNodeModal, setShowSubNodeModal] = useState(false);
8083

8184
function pushNodeChanges(change: any, node_data: any) {
8285
let mutation = gql`mutation {
@@ -324,6 +327,7 @@ export default function Designer({ activeWorkspace, setActiveWorkspace, flow }:
324327
const onEdgesChange = useCallback(
325328
(changes: any) => {
326329
for (let change of changes) {
330+
console.log(change);
327331
if (change.type === 'select' && change.selected === true) {
328332
setSelectedEdge(change.id);
329333
}
@@ -345,11 +349,14 @@ export default function Designer({ activeWorkspace, setActiveWorkspace, flow }:
345349

346350
const onConnect = useCallback(
347351
(changes: any) => {
352+
console.log(changes);
348353
setEdges((eds: any) => {
349354
changes.id = uuidv4();
350-
return addEdge(changes, eds);
355+
let res = addEdge(changes, eds);
356+
if (res.length > eds.length)
357+
pushEdgeConnection(changes);
358+
return res;
351359
})
352-
pushEdgeConnection(changes);
353360
},
354361
[flow],
355362
);
@@ -421,18 +428,27 @@ export default function Designer({ activeWorkspace, setActiveWorkspace, flow }:
421428

422429
<EditNode show={showEditModal} setShow={setShowEditModal} setNodes={setNodes} activeFlow={flow.activeFlow} node={foundNode} />
423430
<NewModal show={showNewModal} setShow={setShowNewModal} setNodes={setNodes} activeFlow={flow.activeFlow} />
431+
<SubNodeModal show={showSubNodeModal} setShow={setShowSubNodeModal} setNodes={setNodes} activeFlow={flow.activeFlow} node={foundNode} />
424432

425433
<NodeToolbar nodeId={selectedNode} position={Position.Top} offset={10} align="start">
426434
<ButtonGroup className="modify" aria-label="modify">
427435
<Button className="btn" variant="Light" size="sm" onClick={() => {
428436
setFoundNode(findNodeById(selectedNode, nodes));
429437
setShowEditModal(true);
430-
}}>Edit</Button>
431-
<Button variant="Light" size="sm" onClick={() => {
432-
deleteElements({ nodes: [{ id: selectedNode }] });
433438
}}>
439+
Edit
440+
</Button>
441+
<Button variant="Light" size="sm" onClick={() => {
442+
deleteElements({ nodes: [{ id: selectedNode }] });
443+
}}>
434444
Delete
435445
</Button>
446+
<Button variant="Light" size="sm" onClick={() => {
447+
setFoundNode(findNodeById(selectedNode, nodes));
448+
setShowSubNodeModal(true);
449+
}}>
450+
Sub Node
451+
</Button>
436452
</ButtonGroup>
437453
</NodeToolbar>
438454

@@ -443,7 +459,7 @@ export default function Designer({ activeWorkspace, setActiveWorkspace, flow }:
443459
<Accordion.Body>
444460
{ selectedEdge ? <div><p>Edge: {selectedEdge} </p><Button variant="light" size="sm" onClick={() => {
445461
deleteElements({ edges: [{ id: selectedEdge }] });
446-
}}>Delete Edge</Button></div> :selectedNode ? <div><p>Node: {selectedNode} </p><p>Type: {findNodeById(selectedNode, nodes).type} </p><Button variant="light" size="sm" onClick={() => {
462+
}}>Delete Edge</Button></div> :selectedNode ? <div><p>Node: {selectedNode} </p><p>Type: {findNodeById(selectedNode, nodes)?.type} </p><Button variant="light" size="sm" onClick={() => {
447463
deleteElements({ nodes: [{ id: selectedNode }] });
448464
}} >Delete Node</Button></div> : <p>-</p> }
449465
</Accordion.Body>

src/designer/sub_node_modal.css

Whitespace-only changes.

src/designer/sub_node_modal.tsx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { useState } from 'react';
2+
import { v4 as uuidv4 } from 'uuid';
3+
import Form from 'react-bootstrap/Form';
4+
import Modal from 'react-bootstrap/Modal';
5+
import Button from 'react-bootstrap/Button';
6+
7+
import { Client, gql } from '../api';
8+
9+
10+
function generateRandomString(length: number) {
11+
const array = new Uint8Array(length);
12+
window.crypto.getRandomValues(array);
13+
return Array.from(array, byte => ('0' + byte.toString(16)).slice(-2)).join('');
14+
}
15+
16+
// Get the current node and edge types to render new node forms
17+
let nodeTypes: any = 0;
18+
function getNodeTypes() {
19+
let query = gql`{
20+
getNodeTypes {
21+
success
22+
message
23+
errors
24+
nodeTypes {
25+
id
26+
name
27+
description
28+
fields
29+
created_at
30+
updated_at
31+
}
32+
}
33+
}`;
34+
Client(query).then((data: any) => {
35+
nodeTypes = data.getNodeTypes.nodeTypes;
36+
})
37+
}
38+
getNodeTypes();
39+
40+
function createNode(node: any, activeFlow: any, node_type_id: any) {
41+
let mutation = gql`mutation {
42+
createNode(node: "${encodeURIComponent(JSON.stringify(node))}", nid: "${node.id}", flow_id: ${ parseInt(activeFlow.id)}, workspace_id: ${parseInt(activeFlow.workspace_id)}, node_type_id: ${node_type_id + 1}) {
43+
success
44+
message
45+
errors
46+
node {
47+
nid
48+
node
49+
flow_id
50+
workspace_id
51+
node_type_id
52+
created_at
53+
updated_at
54+
}
55+
}
56+
}`;
57+
58+
Client(mutation)
59+
}
60+
61+
62+
export function RenderNodeTypeForm(field: any, value: any) {
63+
return (
64+
<Form.Group
65+
className="mb-3"
66+
controlId="NewModal.{field}"
67+
key={field}
68+
>
69+
<Form.Label>{field}</Form.Label>
70+
{value === 'textarea' ? (
71+
<Form.Control name={field} as={value} rows={3} />
72+
) : value === 'text' ? (
73+
<Form.Control name={field} type={value} />
74+
) : value === 'number' ? (
75+
<Form.Control name={field} type={value} />
76+
) : (
77+
<Form.Control name={field} type='text' />
78+
)}
79+
</Form.Group>
80+
)
81+
};
82+
83+
84+
function NewSubNode({ show, setShow, setNodes, activeFlow, node }: { show: boolean, setShow: any, setNodes: any, activeFlow: any, node: any }) {
85+
let fieldIdx = 0;
86+
const handleClose = () => setShow(false);
87+
const [selectValue, setSelectValue] = useState(0);
88+
const nodes: any = ['input', 'output', 'source', 'card', 'default', 'subflow'];
89+
const [nodeType, setNodeType] = useState<any>({ 'id': 1, 'fields': { 'label': 'textarea' } });
90+
91+
function handleSubmit(e: any) {
92+
e.preventDefault();
93+
94+
// Loop over the form fields and add them to the node data field
95+
const formData: { [key: string]: string } = {};
96+
for (let i = 2; i < e.target.length-2; i++) {
97+
const fieldName = e.target[i].name;
98+
const fieldValue = e.target[i].value;
99+
formData[fieldName] = fieldValue;
100+
}
101+
102+
// If the node type is a subflow, add the flow slug to the node data
103+
if (selectValue === 5) {
104+
formData['slug'] = generateRandomString(3);
105+
}
106+
107+
const newNode = {
108+
id: uuidv4(),
109+
position: {
110+
x: node.width/2,
111+
y: node.height/2
112+
},
113+
data: formData,
114+
origin: [0.5, 0.5],
115+
type: nodes[e.target[2].value],
116+
extent: "parent",
117+
parentId: node.id
118+
};
119+
120+
// Update the node list
121+
setNodes((nodes: any) => nodes.concat(newNode))
122+
123+
console.log('node type', nodes);
124+
// Send node data to the server
125+
createNode(newNode, activeFlow, parseInt(e.target[2].value));
126+
127+
// Close the modal
128+
handleClose();
129+
};
130+
131+
return (
132+
<>
133+
<Modal show={show} onHide={handleClose}>
134+
<Form name='NewModal' onSubmit={e => {handleSubmit(e)}}>
135+
<Modal.Header closeButton>
136+
<Modal.Title>Create Node</Modal.Title>
137+
</Modal.Header>
138+
<Modal.Body>
139+
<Form.Group className="mb-3" controlId="NewModal.NodeType">
140+
<Form.Label>Parent Node Id</Form.Label>
141+
<Form.Control name="parentId" type="text" value={node?.id} disabled/>
142+
<Form.Label>Select Node Type:</Form.Label>
143+
<Form.Select aria-label="Default select example" title="SelectNodeType" onChange={(e) => {
144+
setNodeType(nodeTypes[e.target.value]);
145+
setSelectValue(parseInt(e.target.value));
146+
}} value={selectValue}>
147+
{nodeTypes && nodeTypes.map((nType: any) => {
148+
fieldIdx++;
149+
return (<option key={fieldIdx} value={nType.id-1}>{nType.name}</option>);
150+
})
151+
}
152+
</Form.Select>
153+
</Form.Group>
154+
{nodeTypes && Object.keys(nodeType.fields).map((key) => {
155+
return (RenderNodeTypeForm(key, nodeType.fields[key]));
156+
})}
157+
</Modal.Body>
158+
<Modal.Footer>
159+
<Button variant="secondary" onClick={handleClose}>
160+
Close
161+
</Button>
162+
<Button variant="primary" type='submit'>
163+
Create
164+
</Button>
165+
</Modal.Footer>
166+
</Form>
167+
</Modal>
168+
</>
169+
);
170+
}
171+
172+
export default NewSubNode;

0 commit comments

Comments
 (0)