remove linemath and tablemath

This commit is contained in:
2026-03-25 22:39:21 -07:00
parent 7f3dfa8fdf
commit 6de239caa1
11 changed files with 251 additions and 195 deletions

View File

@@ -80,6 +80,23 @@ function sameStringArray(a = [], b = []) {
return a.every((item, index) => item === b[index]);
}
function compareMenuNodes(a, b) {
const orderA = Number.isFinite(a?.def?.menu_order) ? a.def.menu_order : Number.MAX_SAFE_INTEGER;
const orderB = Number.isFinite(b?.def?.menu_order) ? b.def.menu_order : Number.MAX_SAFE_INTEGER;
if (orderA !== orderB) return orderA - orderB;
const nameA = (a?.def?.display_name || a?.className || '').toLowerCase();
const nameB = (b?.def?.display_name || b?.className || '').toLowerCase();
return nameA.localeCompare(nameB);
}
function compareMenuCategories(a, b) {
const orderA = Number.isFinite(a?.order) ? a.order : Number.MAX_SAFE_INTEGER;
const orderB = Number.isFinite(b?.order) ? b.order : Number.MAX_SAFE_INTEGER;
if (orderA !== orderB) return orderA - orderB;
return String(a?.name || '').localeCompare(String(b?.name || ''));
}
function socketTypesCompatible(sourceType, targetType) {
if (sourceType === targetType) return true;
const accepted = SOCKET_COMPATIBILITY[targetType];
@@ -272,10 +289,25 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
}
}
const cat = def.category || 'uncategorized';
if (!cats[cat]) cats[cat] = [];
cats[cat].push({ className, def });
if (!cats[cat]) {
cats[cat] = {
name: cat,
order: Number.isFinite(def.category_order) ? def.category_order : Number.MAX_SAFE_INTEGER,
items: [],
};
}
cats[cat].order = Math.min(
cats[cat].order,
Number.isFinite(def.category_order) ? def.category_order : Number.MAX_SAFE_INTEGER,
);
cats[cat].items.push({ className, def });
}
return cats;
return Object.values(cats)
.map((category) => ({
...category,
items: [...category.items].sort(compareMenuNodes),
}))
.sort(compareMenuCategories);
}, [nodeDefs, filterType, filterDirection]);
// Flat filtered list for search
@@ -283,8 +315,8 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
if (!search.trim()) return null;
const q = search.toLowerCase();
const results = [];
for (const items of Object.values(categories)) {
for (const { className, def } of items) {
for (const category of categories) {
for (const { className, def } of category.items) {
const name = (def.display_name || className).toLowerCase();
if (name.includes(q)) results.push({ className, def });
}
@@ -341,7 +373,7 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
setOpenCat(cat);
}, []);
if (Object.keys(categories).length === 0) {
if (categories.length === 0) {
return (
<div className="context-menu" ref={menuRef} style={{ left: menuPos.x, top: menuPos.y }} onClick={(e) => e.stopPropagation()}>
<div className="context-item" style={{ color: '#64748b' }}>No compatible nodes</div>
@@ -349,7 +381,8 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
);
}
const catNames = Object.keys(categories).sort();
const catNames = categories.map((category) => category.name);
const categoryMap = Object.fromEntries(categories.map((category) => [category.name, category.items]));
return (
<>
@@ -411,7 +444,7 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
</div>
{/* Submenu rendered as a sibling, positioned at computed screen coords */}
{openCat && categories[openCat] && (
{openCat && categoryMap[openCat] && (
<div
className="context-menu ctx-submenu"
ref={subMenuRef}
@@ -423,7 +456,7 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
setOpenCat(null);
}}
>
{categories[openCat].map(({ className, def }) => (
{categoryMap[openCat].map(({ className, def }) => (
<div
key={className}
className="context-item"
@@ -512,9 +545,9 @@ function Flow() {
resolvedPath = getResolvedPathInput(nodeId);
}
if (!resolvedPath) {
if (node.data.className === 'LoadFile') {
if (node.data.className === 'Image') {
resolvedPath = node.data.widgetValues?.filename || '';
} else if (node.data.className === 'LoadDemo') {
} else if (node.data.className === 'ImageDemo') {
resolvedPath = node.data.widgetValues?.name || '';
}
}
@@ -707,7 +740,7 @@ function Flow() {
refreshFolderNodeOutputs(nodeId, value);
}
if (node && (node.data.className === 'LoadFile' || node.data.className === 'LoadDemo') && (name === 'filename' || name === 'name')) {
if (node && (node.data.className === 'Image' || node.data.className === 'ImageDemo') && (name === 'filename' || name === 'name')) {
refreshLoadNodeOutputs(nodeId, value);
}
@@ -803,11 +836,11 @@ function Flow() {
refreshFolderNodeOutputs(newNodeId, widgetValues.folder);
}
// For LoadFile/LoadDemo, auto-fetch channels for the default value
if (className === 'LoadDemo' && widgetValues.name) {
// For Image/ImageDemo, auto-fetch channels for the default value
if (className === 'ImageDemo' && widgetValues.name) {
refreshLoadNodeOutputs(newNodeId, widgetValues.name);
}
if (className === 'LoadFile' && widgetValues.filename) {
if (className === 'Image' && widgetValues.filename) {
refreshLoadNodeOutputs(newNodeId, widgetValues.filename);
}
@@ -924,7 +957,7 @@ function Flow() {
}
});
hydrated.nodes.forEach((node) => {
if (node.data.className === 'LoadFile' || node.data.className === 'LoadDemo') {
if (node.data.className === 'Image' || node.data.className === 'ImageDemo') {
refreshLoadNodeOutputs(node.id);
}
});

View File

@@ -868,10 +868,10 @@ function CustomNode({ id, data }) {
if (data.className === 'Folder') {
return getBasename(data.widgetValues?.folder);
}
if (data.className === 'LoadFile') {
if (data.className === 'Image') {
return getBasename(connectedPathInfo?.path || data.widgetValues?.filename);
}
if (data.className === 'LoadDemo') {
if (data.className === 'ImageDemo') {
return getBasename(data.widgetValues?.name);
}
return '';

View File

@@ -21,14 +21,14 @@ export function getConnectedNodeIds(edges) {
}
function isPreviewLoadNode(node) {
return ['LoadFile', 'LoadDemo'].includes(node?.data?.className);
return ['Image', 'ImageDemo'].includes(node?.data?.className);
}
function hasPreviewLoadSelection(node) {
if (node?.data?.className === 'LoadFile') {
if (node?.data?.className === 'Image') {
return !!String(node.data?.widgetValues?.filename || '').trim();
}
if (node?.data?.className === 'LoadDemo') {
if (node?.data?.className === 'ImageDemo') {
return !!String(node.data?.widgetValues?.name || '').trim();
}
return false;

View File

@@ -12,7 +12,7 @@ test('serializeExecutionGraph excludes isolated nodes from the backend prompt',
{
id: '1',
data: {
className: 'LoadFile',
className: 'Image',
definition: {
input: { required: { filename: ['FILE_PICKER', {}] }, optional: {} },
manual_trigger: false,
@@ -34,7 +34,7 @@ test('serializeExecutionGraph excludes isolated nodes from the backend prompt',
{
id: '3',
data: {
className: 'LoadFile',
className: 'Image',
definition: {
input: { required: { filename: ['FILE_PICKER', {}] }, optional: {} },
manual_trigger: false,
@@ -56,7 +56,7 @@ test('serializeExecutionGraph excludes isolated nodes from the backend prompt',
assert.deepEqual(prompt, {
'1': {
class_type: 'LoadFile',
class_type: 'Image',
inputs: { filename: 'scan.gwy' },
},
'2': {
@@ -72,7 +72,7 @@ test('serializeExecutionGraph includes isolated preview-load nodes alongside con
{
id: '1',
data: {
className: 'LoadFile',
className: 'Image',
definition: {
input: { required: { filename: ['FILE_PICKER', {}] }, optional: {} },
manual_trigger: false,
@@ -94,7 +94,7 @@ test('serializeExecutionGraph includes isolated preview-load nodes alongside con
{
id: '3',
data: {
className: 'LoadDemo',
className: 'ImageDemo',
definition: {
input: { required: { name: [['demo.npy'], {}] }, optional: {} },
manual_trigger: false,
@@ -105,7 +105,7 @@ test('serializeExecutionGraph includes isolated preview-load nodes alongside con
{
id: '4',
data: {
className: 'LoadFile',
className: 'Image',
definition: {
input: { required: { filename: ['FILE_PICKER', {}] }, optional: {} },
manual_trigger: false,
@@ -127,7 +127,7 @@ test('serializeExecutionGraph includes isolated preview-load nodes alongside con
assert.deepEqual(prompt, {
'1': {
class_type: 'LoadFile',
class_type: 'Image',
inputs: { filename: 'first.gwy' },
},
'2': {
@@ -135,19 +135,19 @@ test('serializeExecutionGraph includes isolated preview-load nodes alongside con
inputs: { field: ['1', 0] },
},
'3': {
class_type: 'LoadDemo',
class_type: 'ImageDemo',
inputs: { name: 'demo.npy' },
},
});
assert.equal('4' in prompt, false);
});
test('serializeExecutionGraph allows a singleton LoadFile graph so previews can run', () => {
test('serializeExecutionGraph allows a singleton Image graph so previews can run', () => {
const nodes = [
{
id: '1',
data: {
className: 'LoadFile',
className: 'Image',
definition: {
input: { required: { filename: ['FILE_PICKER', {}] }, optional: {} },
manual_trigger: false,
@@ -161,18 +161,18 @@ test('serializeExecutionGraph allows a singleton LoadFile graph so previews can
assert.deepEqual(prompt, {
'1': {
class_type: 'LoadFile',
class_type: 'Image',
inputs: { filename: 'scan.gwy' },
},
});
});
test('serializeExecutionGraph allows a singleton LoadDemo graph so previews can run', () => {
test('serializeExecutionGraph allows a singleton ImageDemo graph so previews can run', () => {
const nodes = [
{
id: '1',
data: {
className: 'LoadDemo',
className: 'ImageDemo',
definition: {
input: { required: { name: [['demo.npy'], {}] }, optional: {} },
manual_trigger: false,
@@ -186,7 +186,7 @@ test('serializeExecutionGraph allows a singleton LoadDemo graph so previews can
assert.deepEqual(prompt, {
'1': {
class_type: 'LoadDemo',
class_type: 'ImageDemo',
inputs: { name: 'demo.npy' },
},
});
@@ -214,10 +214,10 @@ test('getAutoRunnableNodes ignores disconnected nodes when deciding what can aut
test('getAutoRunnableNodes includes isolated preview-load nodes with selections', () => {
const nodes = [
{ id: '1', data: { className: 'LoadFile', definition: {}, widgetValues: { filename: 'first.gwy' } } },
{ id: '1', data: { className: 'Image', definition: {}, widgetValues: { filename: 'first.gwy' } } },
{ id: '2', data: { className: 'PreviewImage', definition: {}, widgetValues: {} } },
{ id: '3', data: { className: 'LoadDemo', definition: {}, widgetValues: { name: 'demo.npy' } } },
{ id: '4', data: { className: 'LoadFile', definition: {}, widgetValues: { filename: '' } } },
{ id: '3', data: { className: 'ImageDemo', definition: {}, widgetValues: { name: 'demo.npy' } } },
{ id: '4', data: { className: 'Image', definition: {}, widgetValues: { filename: '' } } },
];
const edges = [
{
@@ -233,12 +233,12 @@ test('getAutoRunnableNodes includes isolated preview-load nodes with selections'
assert.deepEqual(runnable.map((node) => node.id), ['1', '2', '3']);
});
test('getAutoRunnableNodes allows a singleton LoadFile graph', () => {
test('getAutoRunnableNodes allows a singleton Image graph', () => {
const nodes = [
{
id: '1',
data: {
className: 'LoadFile',
className: 'Image',
definition: {},
widgetValues: { filename: 'scan.gwy' },
},
@@ -250,12 +250,12 @@ test('getAutoRunnableNodes allows a singleton LoadFile graph', () => {
assert.deepEqual(runnable.map((node) => node.id), ['1']);
});
test('getAutoRunnableNodes allows a singleton LoadDemo graph', () => {
test('getAutoRunnableNodes allows a singleton ImageDemo graph', () => {
const nodes = [
{
id: '1',
data: {
className: 'LoadDemo',
className: 'ImageDemo',
definition: {},
widgetValues: { name: 'demo.npy' },
},

View File

@@ -103,7 +103,7 @@ test('hydrateWorkflowState clears shared path widgets while restoring saved dyna
id: '12',
position: { x: 40, y: 80 },
data: {
className: 'LoadFile',
className: 'Image',
widgetValues: { filename: 'scan.ibw', colormap: 'viridis' },
output: ['DATA_FIELD', 'DATA_FIELD'],
output_name: ['Height', 'Phase'],
@@ -123,7 +123,7 @@ test('hydrateWorkflowState clears shared path widgets while restoring saved dyna
};
const defs = {
LoadFile: {
Image: {
category: 'io',
input: { required: { filename: ['FILE_PICKER', {}], colormap: [['viridis', 'gray'], {}] } },
output: ['DATA_FIELD'],
@@ -138,13 +138,13 @@ test('hydrateWorkflowState clears shared path widgets while restoring saved dyna
assert.deepEqual(hydrated.edges, saved.edges);
assert.equal(hydrated.nodes[0].type, 'custom');
assert.equal(hydrated.nodes[0].dragHandle, '.drag-handle');
assert.equal(hydrated.nodes[0].data.label, 'LoadFile');
assert.equal(hydrated.nodes[0].data.label, 'Image');
assert.equal(hydrated.nodes[0].data.previewImage, null);
assert.equal(hydrated.nodes[0].data.widgetValues.filename, '');
assert.equal(hydrated.nodes[0].data.widgetValues.colormap, 'viridis');
assert.deepEqual(hydrated.nodes[0].data.definition.output, ['DATA_FIELD', 'DATA_FIELD']);
assert.deepEqual(hydrated.nodes[0].data.definition.output_name, ['Height', 'Phase']);
assert.deepEqual(hydrated.nodes[0].data.definition.input, defs.LoadFile.input);
assert.deepEqual(hydrated.nodes[0].data.definition.input, defs.Image.input);
});
test('serializeWorkflowState and hydrateWorkflowState clear path-like widgets but preserve other metadata', () => {
@@ -153,8 +153,8 @@ test('serializeWorkflowState and hydrateWorkflowState clear path-like widgets bu
id: '7',
position: { x: 10, y: 20 },
data: {
label: 'Load File',
className: 'LoadFile',
label: 'Image',
className: 'Image',
widgetValues: { filename: 'scan.gwy', colormap: 'gray' },
definition: {
category: 'io',
@@ -176,7 +176,7 @@ test('serializeWorkflowState and hydrateWorkflowState clear path-like widgets bu
},
];
const defs = {
LoadFile: {
Image: {
category: 'io',
input: { required: { filename: ['FILE_PICKER', {}], colormap: [['gray', 'viridis'], {}] } },
output: ['DATA_FIELD'],