mirror of
https://github.com/ianarawjo/ChainForge.git
synced 2025-03-14 16:26:45 +00:00
commit
eced759218
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,4 +5,5 @@ __pycache__
|
||||
python-backend/cache
|
||||
|
||||
# venv
|
||||
venv
|
||||
venv
|
||||
node_modules
|
||||
|
@ -45,3 +45,8 @@ path.react-flow__edge-path:hover {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rich-editor {
|
||||
min-width: 500px;
|
||||
min-height: 500px;
|
||||
}
|
@ -15,6 +15,7 @@ import EvaluatorNode from './EvaluatorNode';
|
||||
import VisNode from './VisNode';
|
||||
import InspectNode from './InspectorNode';
|
||||
import ScriptNode from './ScriptNode';
|
||||
import CsvNode from './CsvNode';
|
||||
import './text-fields-node.css';
|
||||
|
||||
// State management (from https://reactflow.dev/docs/guides/state-management/)
|
||||
@ -40,7 +41,8 @@ const nodeTypes = {
|
||||
evaluator: EvaluatorNode,
|
||||
vis: VisNode,
|
||||
inspect: InspectNode,
|
||||
script: ScriptNode
|
||||
script: ScriptNode,
|
||||
csv: CsvNode,
|
||||
};
|
||||
|
||||
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} });
|
||||
};
|
||||
|
||||
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)
|
||||
*/
|
||||
@ -201,6 +208,7 @@ const App = () => {
|
||||
<button onClick={addVisNode}>Add vis node</button>
|
||||
<button onClick={addInspectNode}>Add inspect 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={loadFlowFromCache}>Load</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 {
|
||||
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