mirror of
https://github.com/ianarawjo/ChainForge.git
synced 2025-03-15 08:40:48 +00:00
commit
eced759218
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,4 +5,5 @@ __pycache__
|
|||||||
python-backend/cache
|
python-backend/cache
|
||||||
|
|
||||||
# venv
|
# venv
|
||||||
venv
|
venv
|
||||||
|
node_modules
|
||||||
|
@ -45,3 +45,8 @@ path.react-flow__edge-path:hover {
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rich-editor {
|
||||||
|
min-width: 500px;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
@ -15,6 +15,7 @@ import EvaluatorNode from './EvaluatorNode';
|
|||||||
import VisNode from './VisNode';
|
import VisNode from './VisNode';
|
||||||
import InspectNode from './InspectorNode';
|
import InspectNode from './InspectorNode';
|
||||||
import ScriptNode from './ScriptNode';
|
import ScriptNode from './ScriptNode';
|
||||||
|
import CsvNode from './CsvNode';
|
||||||
import './text-fields-node.css';
|
import './text-fields-node.css';
|
||||||
|
|
||||||
// State management (from https://reactflow.dev/docs/guides/state-management/)
|
// State management (from https://reactflow.dev/docs/guides/state-management/)
|
||||||
@ -40,7 +41,8 @@ const nodeTypes = {
|
|||||||
evaluator: EvaluatorNode,
|
evaluator: EvaluatorNode,
|
||||||
vis: VisNode,
|
vis: VisNode,
|
||||||
inspect: InspectNode,
|
inspect: InspectNode,
|
||||||
script: ScriptNode
|
script: ScriptNode,
|
||||||
|
csv: CsvNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectionLineStyle = { stroke: '#ddd' };
|
const connectionLineStyle = { stroke: '#ddd' };
|
||||||
@ -91,6 +93,11 @@ const App = () => {
|
|||||||
addNode({ id: 'scriptNode-'+Date.now(), type: 'script', data: {}, position: {x: x-200, y:y-100} });
|
addNode({ id: 'scriptNode-'+Date.now(), type: 'script', data: {}, position: {x: x-200, y:y-100} });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addCsvNode = (event) => {
|
||||||
|
const { x, y } = getViewportCenter();
|
||||||
|
addNode({ id: 'csvNode-'+Date.now(), type: 'csv', data: {}, position: {x: x-200, y:y-100} });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SAVING / LOADING, IMPORT / EXPORT (from JSON)
|
* SAVING / LOADING, IMPORT / EXPORT (from JSON)
|
||||||
*/
|
*/
|
||||||
@ -201,6 +208,7 @@ const App = () => {
|
|||||||
<button onClick={addVisNode}>Add vis node</button>
|
<button onClick={addVisNode}>Add vis node</button>
|
||||||
<button onClick={addInspectNode}>Add inspect node</button>
|
<button onClick={addInspectNode}>Add inspect node</button>
|
||||||
<button onClick={addScriptNode}>Add script node</button>
|
<button onClick={addScriptNode}>Add script node</button>
|
||||||
|
<button onClick={addCsvNode}>Add csv node</button>
|
||||||
<button onClick={saveFlow} style={{marginLeft: '12px'}}>Save</button>
|
<button onClick={saveFlow} style={{marginLeft: '12px'}}>Save</button>
|
||||||
<button onClick={loadFlowFromCache}>Load</button>
|
<button onClick={loadFlowFromCache}>Load</button>
|
||||||
<button onClick={exportFlow} style={{marginLeft: '12px'}}>Export</button>
|
<button onClick={exportFlow} style={{marginLeft: '12px'}}>Export</button>
|
||||||
|
129
chain-forge/src/CsvNode.js
Normal file
129
chain-forge/src/CsvNode.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
|
import { Badge, Text } from '@mantine/core';
|
||||||
|
import useStore from './store';
|
||||||
|
import NodeLabel from './NodeLabelComponent'
|
||||||
|
import { IconCsv } from '@tabler/icons-react';
|
||||||
|
import { Handle } from 'react-flow-renderer';
|
||||||
|
|
||||||
|
const CsvNode = ({ data, id }) => {
|
||||||
|
const setDataPropsForNode = useStore((state) => state.setDataPropsForNode);
|
||||||
|
const [contentDiv, setContentDiv] = useState(null);
|
||||||
|
const [isEditing, setIsEditing] = useState(true);
|
||||||
|
const [csvInput, setCsvInput] = useState(null);
|
||||||
|
const [countText, setCountText] = useState(null);
|
||||||
|
|
||||||
|
// initializing
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data.fields) {
|
||||||
|
setDataPropsForNode(id, { text: '', fields: [] });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const processCsv = (csv) => {
|
||||||
|
var matches = csv.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
|
||||||
|
for (var n = 0; n < matches.length; ++n) {
|
||||||
|
matches[n] = matches[n].trim();
|
||||||
|
if (matches[n] == ',') matches[n] = '';
|
||||||
|
}
|
||||||
|
return matches.map(e => e.trim()).filter(e => e.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// const processCsv = (csv) => {
|
||||||
|
// if (!csv) return [];
|
||||||
|
// // Split the input string by rows, and merge
|
||||||
|
// var res = csv.split('\n').join(',');
|
||||||
|
|
||||||
|
// // remove all the empty or whitespace-only elements
|
||||||
|
// return res.split(',').map(e => e.trim()).filter(e => e.length > 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Handle a change in a text fields' input.
|
||||||
|
const handleInputChange = useCallback((event) => {
|
||||||
|
// Update the data for this text fields' id.
|
||||||
|
let new_data = { 'text': event.target.value, 'fields': processCsv(event.target.value) };
|
||||||
|
setDataPropsForNode(id, new_data);
|
||||||
|
}, [id, setDataPropsForNode]);
|
||||||
|
|
||||||
|
const handKeyDown = useCallback((event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
setIsEditing(false);
|
||||||
|
setCsvInput(null);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// handling Div Click
|
||||||
|
const handleDivOnClick = useCallback((event) => {
|
||||||
|
setIsEditing(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOnBlur = useCallback((event) => {
|
||||||
|
setIsEditing(false);
|
||||||
|
setCsvInput(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// render csv div
|
||||||
|
const renderCsvDiv = useCallback(() => {
|
||||||
|
// Take the data.text as csv (only 1 row), and get individual elements
|
||||||
|
const elements = data.fields;
|
||||||
|
|
||||||
|
// generate a HTML code that highlights the elements
|
||||||
|
const html = [];
|
||||||
|
elements.forEach((e, idx) => {
|
||||||
|
// html.push(<Badge color="orange" size="lg" radius="sm">{e}</Badge>)
|
||||||
|
html.push(<span key={idx} className="csv-element">{e}</span>);
|
||||||
|
if (idx < elements.length - 1) {
|
||||||
|
html.push(<span key={idx + 'comma'} className="csv-comma">,</span>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setContentDiv(<div className='csv-div nowheel' onClick={handleDivOnClick}>
|
||||||
|
{html}
|
||||||
|
</div>);
|
||||||
|
setCountText(<Text size="xs" style={{ marginTop: '5px' }} color='blue' align='right'>{elements.length} elements</Text>);
|
||||||
|
}, [data.text, handleDivOnClick]);
|
||||||
|
|
||||||
|
// When isEditing changes, add input
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEditing) {
|
||||||
|
setCsvInput(null);
|
||||||
|
renderCsvDiv();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!csvInput) {
|
||||||
|
var text_val = data.text || '';
|
||||||
|
setCsvInput(
|
||||||
|
<div className="input-field" key={id}>
|
||||||
|
<textarea id={id} name={id} className="text-field-fixed nodrag csv-input" rows="2" cols="40" defaultValue={text_val} onChange={handleInputChange} placeholder='Paste your CSV text here' onKeyDown={handKeyDown} onBlur={handleOnBlur} autoFocus={true}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
setContentDiv(null);
|
||||||
|
setCountText(null);
|
||||||
|
}
|
||||||
|
}, [isEditing]);
|
||||||
|
|
||||||
|
// when data.text changes, update the content div
|
||||||
|
useEffect(() => {
|
||||||
|
// When in editing mode, don't update the content div
|
||||||
|
if (isEditing) return;
|
||||||
|
if (!data.text) return;
|
||||||
|
renderCsvDiv();
|
||||||
|
|
||||||
|
}, [id, data.text]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-fields-node cfnode">
|
||||||
|
<NodeLabel title={data.title || 'CSV Node'} nodeId={id} icon={<IconCsv size="16px" />} />
|
||||||
|
{csvInput}
|
||||||
|
{contentDiv}
|
||||||
|
{countText ? countText : <></>}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position="right"
|
||||||
|
id="output"
|
||||||
|
style={{ top: "50%", background: '#555' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CsvNode;
|
@ -21,4 +21,36 @@ code {
|
|||||||
|
|
||||||
.script-node-input {
|
.script-node-input {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-element {
|
||||||
|
position: relative;
|
||||||
|
color: #8a3e07;
|
||||||
|
background-color: #FFE8CC;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: monospace;
|
||||||
|
padding: .2em .4em;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 4px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set a muted text */
|
||||||
|
.csv-comma {
|
||||||
|
color: #FFC107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-div {
|
||||||
|
width: 350px;
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csv-input {
|
||||||
|
width: 350px;
|
||||||
|
height: 150px;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user