rename grains to particle, add colormap adjust, table math
This commit is contained in:
@@ -178,6 +178,7 @@ function serializeGraph(nodes, edges, { excludeManualTrigger = false } = {}) {
|
||||
for (const [name, spec] of Object.entries(required)) {
|
||||
const [type] = Array.isArray(spec) ? spec : [spec];
|
||||
if (DATA_TYPES.has(type)) continue; // socket, handled via edges
|
||||
if (type === 'BUTTON') continue; // UI-only widget, not a backend input
|
||||
if (widgetValues[name] !== undefined) {
|
||||
inputs[name] = widgetValues[name];
|
||||
}
|
||||
@@ -604,6 +605,7 @@ function Flow() {
|
||||
for (const [name, spec] of Object.entries(required)) {
|
||||
const [type, opts] = Array.isArray(spec) ? spec : [spec, {}];
|
||||
if (DATA_TYPES.has(type)) continue;
|
||||
if (type === 'BUTTON') continue;
|
||||
if (Array.isArray(type)) {
|
||||
widgetValues[name] = type[0]; // combo default = first option
|
||||
} else {
|
||||
@@ -1026,7 +1028,7 @@ function Flow() {
|
||||
const cat = n.data?.definition?.category;
|
||||
const colors = {
|
||||
io: '#37474f', filters: '#1a237e', level: '#1b5e20',
|
||||
analysis: '#4a148c', grains: '#bf360c', display: '#212121',
|
||||
analysis: '#4a148c', particles: '#bf360c', display: '#212121',
|
||||
};
|
||||
return colors[cat] || '#333';
|
||||
}}
|
||||
|
||||
@@ -26,7 +26,7 @@ const CAT_COLORS = {
|
||||
modify: '#0f766e',
|
||||
level: '#1b5e20',
|
||||
analysis: '#4a148c',
|
||||
grains: '#bf360c',
|
||||
particles:'#bf360c',
|
||||
display: '#212121',
|
||||
};
|
||||
|
||||
@@ -171,6 +171,69 @@ function CollapsibleSection({ title, defaultOpen, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
function getTableColumns(rows) {
|
||||
const columns = [];
|
||||
for (const row of rows) {
|
||||
if (!row || typeof row !== 'object') continue;
|
||||
for (const key of Object.keys(row)) {
|
||||
if (!columns.includes(key)) columns.push(key);
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
function formatTableCell(value) {
|
||||
if (value == null) return '';
|
||||
if (typeof value === 'number') {
|
||||
if (!Number.isFinite(value)) return String(value);
|
||||
const abs = Math.abs(value);
|
||||
if (Number.isInteger(value) && abs < 1e6) return String(value);
|
||||
if ((abs > 0 && abs < 1e-3) || abs >= 1e4) return value.toExponential(3);
|
||||
return value.toFixed(4).replace(/\.?0+$/, '');
|
||||
}
|
||||
if (Array.isArray(value)) return value.join(', ');
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function NodeTable({ rows }) {
|
||||
const columns = getTableColumns(rows);
|
||||
if (columns.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="node-table-wrap">
|
||||
<div className="node-table-scroll">
|
||||
<table className="node-table-grid">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((column) => (
|
||||
<th key={column} scope="col">{column}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<tr key={row.id ?? row.quantity ?? rowIndex}>
|
||||
{columns.map((column) => {
|
||||
const value = row?.[column];
|
||||
return (
|
||||
<td
|
||||
key={`${rowIndex}-${column}`}
|
||||
className={typeof value === 'number' ? 'node-table-num' : ''}
|
||||
title={formatTableCell(value)}
|
||||
>
|
||||
{formatTableCell(value)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── CustomNode component ──────────────────────────────────────────────
|
||||
|
||||
function CustomNode({ id, data }) {
|
||||
@@ -411,21 +474,7 @@ function CustomNode({ id, data }) {
|
||||
{/* Collapsible table data */}
|
||||
{data.tableRows && data.tableRows.length > 0 && (
|
||||
<CollapsibleSection title="Table" defaultOpen={true}>
|
||||
<div className="node-table">
|
||||
{data.tableRows.map((row, i) => {
|
||||
let line;
|
||||
if (row.quantity !== undefined) {
|
||||
const val = typeof row.value === 'number' ? row.value.toExponential(3) : row.value;
|
||||
line = `${row.quantity}: ${val} ${row.unit || ''}`;
|
||||
} else {
|
||||
line = Object.entries(row)
|
||||
.slice(0, 3)
|
||||
.map(([k, v]) => `${k}: ${typeof v === 'number' ? v.toExponential(2) : v}`)
|
||||
.join(' ');
|
||||
}
|
||||
return <div key={i} className="table-line">{line}</div>;
|
||||
})}
|
||||
</div>
|
||||
<NodeTable rows={data.tableRows} />
|
||||
</CollapsibleSection>
|
||||
)}
|
||||
</div>
|
||||
@@ -480,6 +529,26 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'BUTTON') {
|
||||
const updates = opts?.set_widgets && typeof opts.set_widgets === 'object'
|
||||
? Object.entries(opts.set_widgets)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<button
|
||||
className="nodrag widget-button"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
for (const [targetName, targetValue] of updates) {
|
||||
onChange(nodeId, targetName, targetValue);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{opts?.label || name}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'FLOAT') {
|
||||
if (opts?.slider) {
|
||||
const rawMin = opts?.min_widget ? widgetValues?.[opts.min_widget] : opts?.min;
|
||||
|
||||
@@ -227,6 +227,23 @@ html, body, #root {
|
||||
accent-color: #3a7abf;
|
||||
}
|
||||
|
||||
.widget-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: #0f3460;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 3px;
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.widget-button:hover {
|
||||
background: #1a4a8a;
|
||||
border-color: #3a7abf;
|
||||
}
|
||||
|
||||
.slider-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -496,18 +513,56 @@ html, body, #root {
|
||||
}
|
||||
|
||||
/* ── Node table ────────────────────────────────────────────────────── */
|
||||
.node-table {
|
||||
padding: 4px 10px;
|
||||
.node-table-wrap {
|
||||
padding: 4px 10px 8px;
|
||||
}
|
||||
|
||||
.node-table-scroll {
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 6px;
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.node-table-grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: "SF Mono", "Fira Code", monospace;
|
||||
font-size: 10px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.table-line {
|
||||
.node-table-grid th,
|
||||
.node-table-grid td {
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid rgba(51, 65, 85, 0.75);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.node-table-grid thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: #16213e;
|
||||
color: #94a3b8;
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.node-table-grid tbody tr:nth-child(even) {
|
||||
background: rgba(30, 41, 59, 0.38);
|
||||
}
|
||||
|
||||
.node-table-grid tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.node-table-num {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
/* ── Node resize handles ───────────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user