fix table math column picker

This commit is contained in:
2026-03-25 00:01:24 -07:00
parent 44de72d31b
commit a65b7c5642
9 changed files with 174 additions and 9 deletions

View File

@@ -451,6 +451,9 @@ function Flow() {
case 'table':
updateNodeData(msg.data.node_id, { tableRows: msg.data.rows });
break;
case 'scalar':
updateNodeData(msg.data.node_id, { scalarValue: msg.data.value });
break;
case 'mesh3d':
updateNodeData(msg.data.node_id, { meshData: msg.data.mesh });
break;
@@ -628,6 +631,7 @@ function Flow() {
tableRows: null,
meshData: null,
overlay: null,
scalarValue: null,
},
};

View File

@@ -1,4 +1,4 @@
import React, { useContext, useRef, useCallback, useState, memo, lazy, Suspense } from 'react';
import React, { useContext, useRef, useCallback, useState, useEffect, memo, lazy, Suspense } from 'react';
import { Handle, Position, useStore } from '@xyflow/react';
import LinePlotOverlay from './LinePlotOverlay';
@@ -195,6 +195,16 @@ function formatTableCell(value) {
return String(value);
}
function formatScalarValue(value) {
if (value == null || Number.isNaN(Number(value))) return '—';
const numeric = Number(value);
if (!Number.isFinite(numeric)) return String(numeric);
const abs = Math.abs(numeric);
if (abs === 0) return '0';
if ((abs > 0 && abs < 1e-3) || abs >= 1e5) return numeric.toExponential(4);
return numeric.toFixed(abs >= 100 ? 2 : 4).replace(/\.?0+$/, '');
}
function NodeTable({ rows }) {
const columns = getTableColumns(rows);
if (columns.length === 0) return null;
@@ -362,6 +372,13 @@ function CustomNode({ id, data }) {
<div className="node-warning">{data.warning}</div>
)}
{typeof data.scalarValue === 'number' && (
<div className="node-value-display">
<div className="node-value-label">Value</div>
<div className="node-value-box">{formatScalarValue(data.scalarValue)}</div>
</div>
)}
{/* Widget rows */}
{widgets.map((w) => (
<div className={`widget-row${w.socketType ? ' widget-row-socket' : ''}`} key={w.name}>
@@ -487,6 +504,29 @@ function CustomNode({ id, data }) {
function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFileBrowser }) {
const { name, type, opts } = widget;
const val = value ?? opts?.default ?? '';
const dynamicTableColumns = useStore(
useCallback(
(s) => {
const tableInputName = opts?.choices_from_table_input;
if (!tableInputName) return [];
const targetHandle = `input::${tableInputName}::TABLE`;
const edge = s.edges?.find((e) => e.target === nodeId && e.targetHandle === targetHandle);
if (!edge) return [];
const sourceNode = s.nodeLookup?.get(edge.source) || s.nodes?.find((n) => n.id === edge.source);
const rows = sourceNode?.data?.tableRows;
return Array.isArray(rows) ? getTableColumns(rows) : [];
},
[nodeId, opts?.choices_from_table_input],
),
);
useEffect(() => {
if (!opts?.choices_from_table_input || dynamicTableColumns.length === 0) return;
const current = String(val ?? '');
if (dynamicTableColumns.includes(current)) return;
const preferred = dynamicTableColumns.includes('value') ? 'value' : dynamicTableColumns[0];
if (preferred != null) onChange(nodeId, name, preferred);
}, [dynamicTableColumns, name, nodeId, onChange, opts?.choices_from_table_input, val]);
// Combo / enum — type itself is the array of options
if (Array.isArray(type)) {
@@ -506,6 +546,24 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile
);
}
if (type === 'STRING' && opts?.choices_from_table_input && dynamicTableColumns.length > 0) {
const selected = dynamicTableColumns.includes(String(val)) ? String(val) : dynamicTableColumns[0];
return (
<>
<label>{name}</label>
<select
className="nodrag"
value={selected}
onChange={(e) => onChange(nodeId, name, e.target.value)}
>
{dynamicTableColumns.map((column) => (
<option key={column} value={column}>{column}</option>
))}
</select>
</>
);
}
if (type === 'FILE_PICKER') {
return (
<>

View File

@@ -161,6 +161,39 @@ html, body, #root {
border-bottom: 1px solid rgba(251, 191, 36, 0.2);
}
.node-value-display {
padding: 8px 10px 4px;
}
.node-value-label {
font-size: 9px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #7dd3fc;
margin-bottom: 5px;
}
.node-value-box {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid rgba(125, 211, 252, 0.45);
background:
linear-gradient(180deg, rgba(14, 116, 144, 0.2), rgba(8, 47, 73, 0.45)),
linear-gradient(135deg, rgba(125, 211, 252, 0.08), rgba(56, 189, 248, 0.02));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 8px 20px rgba(2, 132, 199, 0.14);
color: #e0f2fe;
font-size: 22px;
font-weight: 700;
line-height: 1.1;
letter-spacing: 0.02em;
text-align: center;
font-variant-numeric: tabular-nums lining-nums;
overflow-wrap: anywhere;
}
/* ── I/O rows ──────────────────────────────────────────────────────── */
.io-row {
display: flex;

View File

@@ -39,6 +39,7 @@ export function hydrateWorkflowState(data, defs = {}) {
tableRows: null,
meshData: null,
overlay: null,
scalarValue: null,
},
}));