remove linemath and tablemath
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user