Spaces:
Sleeping
Sleeping
Commit ·
528f558
1
Parent(s): 30a2a5d
Add Advanced Feature option (make it more clear)
Browse files- frontend/src/CustomNode.css +55 -3
- frontend/src/CustomNode.js +50 -16
frontend/src/CustomNode.css
CHANGED
|
@@ -33,7 +33,7 @@
|
|
| 33 |
.custom-node-input-row {
|
| 34 |
display: flex;
|
| 35 |
flex-direction: column;
|
| 36 |
-
margin-bottom:
|
| 37 |
}
|
| 38 |
|
| 39 |
.custom-node-input-row:last-child {
|
|
@@ -42,7 +42,7 @@
|
|
| 42 |
|
| 43 |
.custom-node-input-row label {
|
| 44 |
font-size: 12px;
|
| 45 |
-
margin-bottom:
|
| 46 |
font-weight: bold;
|
| 47 |
color: #555;
|
| 48 |
}
|
|
@@ -52,7 +52,7 @@
|
|
| 52 |
.custom-node-input-row textarea,
|
| 53 |
.custom-node-input-row select {
|
| 54 |
width: 100%;
|
| 55 |
-
padding:
|
| 56 |
border: 1px solid #ccc;
|
| 57 |
border-radius: 4px;
|
| 58 |
box-sizing: border-box;
|
|
@@ -76,3 +76,55 @@
|
|
| 76 |
margin-bottom: 0;
|
| 77 |
font-weight: normal;
|
| 78 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
.custom-node-input-row {
|
| 34 |
display: flex;
|
| 35 |
flex-direction: column;
|
| 36 |
+
margin-bottom: 12px;
|
| 37 |
}
|
| 38 |
|
| 39 |
.custom-node-input-row:last-child {
|
|
|
|
| 42 |
|
| 43 |
.custom-node-input-row label {
|
| 44 |
font-size: 12px;
|
| 45 |
+
margin-bottom: 5px;
|
| 46 |
font-weight: bold;
|
| 47 |
color: #555;
|
| 48 |
}
|
|
|
|
| 52 |
.custom-node-input-row textarea,
|
| 53 |
.custom-node-input-row select {
|
| 54 |
width: 100%;
|
| 55 |
+
padding: 8px;
|
| 56 |
border: 1px solid #ccc;
|
| 57 |
border-radius: 4px;
|
| 58 |
box-sizing: border-box;
|
|
|
|
| 76 |
margin-bottom: 0;
|
| 77 |
font-weight: normal;
|
| 78 |
}
|
| 79 |
+
|
| 80 |
+
.file-input-wrapper {
|
| 81 |
+
display: flex;
|
| 82 |
+
align-items: center;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.file-input-wrapper input[type="file"] {
|
| 86 |
+
flex-grow: 1;
|
| 87 |
+
font-size: 11px;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.file-input-wrapper button {
|
| 91 |
+
padding: 6px 10px;
|
| 92 |
+
font-size: 12px;
|
| 93 |
+
background-color: #007bff;
|
| 94 |
+
color: white;
|
| 95 |
+
border: none;
|
| 96 |
+
border-radius: 4px;
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
margin-left: 8px;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.file-input-wrapper button:hover {
|
| 102 |
+
background-color: #0056b3;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.advanced-settings {
|
| 106 |
+
margin-top: 15px;
|
| 107 |
+
border-top: 1px solid #eee;
|
| 108 |
+
padding-top: 10px;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.advanced-settings-toggle {
|
| 112 |
+
background: none;
|
| 113 |
+
border: none;
|
| 114 |
+
cursor: pointer;
|
| 115 |
+
font-size: 12px;
|
| 116 |
+
font-weight: bold;
|
| 117 |
+
color: #007bff;
|
| 118 |
+
padding: 0;
|
| 119 |
+
margin-bottom: 10px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.advanced-settings-toggle:hover {
|
| 123 |
+
text-decoration: underline;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.advanced-settings-content {
|
| 127 |
+
padding: 10px;
|
| 128 |
+
background-color: #f0f0f0;
|
| 129 |
+
border-radius: 4px;
|
| 130 |
+
}
|
frontend/src/CustomNode.js
CHANGED
|
@@ -1,7 +1,26 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
import { Handle, Position } from 'reactflow';
|
| 3 |
import './CustomNode.css';
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
const renderInput = (input) => {
|
| 6 |
const { type, label, props = {} } = input;
|
| 7 |
const id = `input-${input.id}`;
|
|
@@ -11,7 +30,7 @@ const renderInput = (input) => {
|
|
| 11 |
return (
|
| 12 |
<div key={id} className="custom-node-input-row">
|
| 13 |
<label htmlFor={id}>{label}</label>
|
| 14 |
-
<textarea id={id} rows={
|
| 15 |
</div>
|
| 16 |
);
|
| 17 |
case 'number':
|
|
@@ -22,18 +41,8 @@ const renderInput = (input) => {
|
|
| 22 |
</div>
|
| 23 |
);
|
| 24 |
case 'slider':
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
<label htmlFor={id}>{label} ({props.value || props.minimum})</label>
|
| 28 |
-
<input
|
| 29 |
-
id={id}
|
| 30 |
-
type="range"
|
| 31 |
-
min={props.minimum || 0}
|
| 32 |
-
max={props.maximum || 100}
|
| 33 |
-
defaultValue={props.value || props.minimum}
|
| 34 |
-
/>
|
| 35 |
-
</div>
|
| 36 |
-
);
|
| 37 |
case 'checkbox':
|
| 38 |
return (
|
| 39 |
<div key={id} className="custom-node-input-row checkbox">
|
|
@@ -61,7 +70,10 @@ const renderInput = (input) => {
|
|
| 61 |
return (
|
| 62 |
<div key={id} className="custom-node-input-row">
|
| 63 |
<label htmlFor={id}>{label} ({type})</label>
|
| 64 |
-
<
|
|
|
|
|
|
|
|
|
|
| 65 |
</div>
|
| 66 |
);
|
| 67 |
default:
|
|
@@ -75,6 +87,12 @@ const renderInput = (input) => {
|
|
| 75 |
};
|
| 76 |
|
| 77 |
const CustomNode = ({ data }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
return (
|
| 79 |
<div className="custom-node">
|
| 80 |
<Handle type="target" position={Position.Top} />
|
|
@@ -83,7 +101,23 @@ const CustomNode = ({ data }) => {
|
|
| 83 |
<p>/{data.apiName}</p>
|
| 84 |
</div>
|
| 85 |
<div className="custom-node-content">
|
| 86 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
</div>
|
| 88 |
<Handle type="source" position={Position.Bottom} />
|
| 89 |
</div>
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
import { Handle, Position } from 'reactflow';
|
| 3 |
import './CustomNode.css';
|
| 4 |
|
| 5 |
+
// A dedicated component for the slider to manage its own state
|
| 6 |
+
const SliderInput = ({ id, label, props }) => {
|
| 7 |
+
const [value, setValue] = useState(props.value || props.minimum);
|
| 8 |
+
|
| 9 |
+
return (
|
| 10 |
+
<div className="custom-node-input-row">
|
| 11 |
+
<label htmlFor={id}>{label} ({value})</label>
|
| 12 |
+
<input
|
| 13 |
+
id={id}
|
| 14 |
+
type="range"
|
| 15 |
+
min={props.minimum || 0}
|
| 16 |
+
max={props.maximum || 100}
|
| 17 |
+
value={value}
|
| 18 |
+
onChange={(e) => setValue(e.target.value)}
|
| 19 |
+
/>
|
| 20 |
+
</div>
|
| 21 |
+
);
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
const renderInput = (input) => {
|
| 25 |
const { type, label, props = {} } = input;
|
| 26 |
const id = `input-${input.id}`;
|
|
|
|
| 30 |
return (
|
| 31 |
<div key={id} className="custom-node-input-row">
|
| 32 |
<label htmlFor={id}>{label}</label>
|
| 33 |
+
<textarea id={id} rows={3} defaultValue={props.value || ''} />
|
| 34 |
</div>
|
| 35 |
);
|
| 36 |
case 'number':
|
|
|
|
| 41 |
</div>
|
| 42 |
);
|
| 43 |
case 'slider':
|
| 44 |
+
// Use the new stateful component for sliders
|
| 45 |
+
return <SliderInput key={id} id={id} label={label} props={props} />;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
case 'checkbox':
|
| 47 |
return (
|
| 48 |
<div key={id} className="custom-node-input-row checkbox">
|
|
|
|
| 70 |
return (
|
| 71 |
<div key={id} className="custom-node-input-row">
|
| 72 |
<label htmlFor={id}>{label} ({type})</label>
|
| 73 |
+
<div className="file-input-wrapper">
|
| 74 |
+
<input id={id} type="file" />
|
| 75 |
+
<button>Upload</button>
|
| 76 |
+
</div>
|
| 77 |
</div>
|
| 78 |
);
|
| 79 |
default:
|
|
|
|
| 87 |
};
|
| 88 |
|
| 89 |
const CustomNode = ({ data }) => {
|
| 90 |
+
const [isAdvancedVisible, setAdvancedVisible] = useState(false);
|
| 91 |
+
|
| 92 |
+
const primaryTypes = ['textbox', 'image', 'audio', 'video', 'file'];
|
| 93 |
+
const primaryInputs = data.inputs?.filter(input => primaryTypes.includes(input.type));
|
| 94 |
+
const advancedInputs = data.inputs?.filter(input => !primaryTypes.includes(input.type));
|
| 95 |
+
|
| 96 |
return (
|
| 97 |
<div className="custom-node">
|
| 98 |
<Handle type="target" position={Position.Top} />
|
|
|
|
| 101 |
<p>/{data.apiName}</p>
|
| 102 |
</div>
|
| 103 |
<div className="custom-node-content">
|
| 104 |
+
{primaryInputs?.map(renderInput)}
|
| 105 |
+
|
| 106 |
+
{advancedInputs && advancedInputs.length > 0 && (
|
| 107 |
+
<div className="advanced-settings">
|
| 108 |
+
<button
|
| 109 |
+
className="advanced-settings-toggle"
|
| 110 |
+
onClick={() => setAdvancedVisible(!isAdvancedVisible)}
|
| 111 |
+
>
|
| 112 |
+
{isAdvancedVisible ? '▼' : '►'} Advanced Settings
|
| 113 |
+
</button>
|
| 114 |
+
{isAdvancedVisible && (
|
| 115 |
+
<div className="advanced-settings-content">
|
| 116 |
+
{advancedInputs.map(renderInput)}
|
| 117 |
+
</div>
|
| 118 |
+
)}
|
| 119 |
+
</div>
|
| 120 |
+
)}
|
| 121 |
</div>
|
| 122 |
<Handle type="source" position={Position.Bottom} />
|
| 123 |
</div>
|