feat: Add node sorting by ID to the info panel and pop-out viewer.

This commit is contained in:
AEmotionStudio
2026-01-05 16:44:37 -08:00
parent e2feb5739a
commit a02efd032d
10 changed files with 86 additions and 9 deletions
+13
View File
@@ -66,6 +66,19 @@ export class NodeSelector {
.sort((a, b) => a.order - b.order);
}
/**
* Get nodes sorted by ID number (ascending).
*/
getNodesSortedById(): NodeListEntry[] {
return this.getAllNodes()
.map(n => ({
id: n.id,
title: n.title || 'Untitled',
type: n.type || 'Unknown'
}))
.sort((a, b) => a.id - b.id);
}
/**
* Get a node by its ID.
*/
+18 -2
View File
@@ -761,7 +761,8 @@ export class UIManager {
// Node Details section
if (info.hoveredNode) {
const nodeContent: any[] = [
{ label: 'Title', value: info.hoveredNode.title, clickable: 'title' }
{ label: 'Title', value: info.hoveredNode.title, clickable: 'title' },
{ label: 'ID', value: `#${info.hoveredNode.id}`, clickable: 'id' }
];
// Add execution order if available
@@ -910,6 +911,8 @@ export class UIManager {
this.showTitleDropdown(row as HTMLElement);
} else if (clickableType === 'execOrder') {
this.showExecOrderDropdown(row as HTMLElement);
} else if (clickableType === 'id') {
this.showIdDropdown(row as HTMLElement);
}
});
@@ -1015,13 +1018,26 @@ export class UIManager {
this.createDropdown(nodes, anchorElement, 'execOrder');
}
/**
* Show dropdown with nodes sorted by ID.
* @param anchorElement - Element to anchor the dropdown to
*/
showIdDropdown(anchorElement: HTMLElement): void {
this.hideDropdown();
const nodes = this.nodeSelector.getNodesSortedById();
if (nodes.length === 0) return;
this.createDropdown(nodes, anchorElement, 'id');
}
/**
* Create and show the dropdown.
*/
private createDropdown(
nodes: NodeListEntry[] | NodeExecOrderEntry[],
anchorElement: HTMLElement,
type: 'title' | 'execOrder'
type: 'title' | 'execOrder' | 'id'
): void {
const dropdown = document.createElement('div');
dropdown.className = `node-selector-dropdown theme-${this.stateManager.state.currentTheme}`;
+11 -3
View File
@@ -22,7 +22,7 @@ interface PopOutMessage {
*/
interface NodeListData {
nodes: { id: number; title: string; type: string; order?: number }[];
sortBy: 'title' | 'execOrder';
sortBy: 'title' | 'execOrder' | 'id';
}
interface PopOutConfig {
@@ -180,7 +180,7 @@ export class PopOutManager {
case 'request-nodes':
// Popout is requesting the node list for dropdown
Logger.debug('[PopOut] Received request-nodes:', message.data);
this.handleNodeListRequest(message.data as 'title' | 'execOrder');
this.handleNodeListRequest(message.data as 'title' | 'execOrder' | 'id');
break;
case 'node-select':
// Popout selected a node, notify main window
@@ -340,7 +340,7 @@ export class PopOutManager {
* Handle node list request from popout viewer.
* Fetches nodes from canvas and sends them to the popout.
*/
private handleNodeListRequest(sortBy: 'title' | 'execOrder'): void {
private handleNodeListRequest(sortBy: 'title' | 'execOrder' | 'id'): void {
if (!this.channel) return;
try {
@@ -360,6 +360,14 @@ export class PopOutManager {
}))
.filter((n: any) => n.order >= 0)
.sort((a: any, b: any) => a.order - b.order);
} else if (sortBy === 'id') {
nodeList = nodes
.map((n: any) => ({
id: n.id,
title: n.title || 'Untitled',
type: n.type || 'Unknown'
}))
.sort((a: any, b: any) => a.id - b.id);
} else {
nodeList = nodes
.map((n: any) => ({
+10
View File
@@ -28,6 +28,16 @@ class NodeSelector {
order: n.order ?? -1
})).filter((n) => n.order >= 0).sort((a, b) => a.order - b.order);
}
/**
* Get nodes sorted by ID number (ascending).
*/
getNodesSortedById() {
return this.getAllNodes().map((n) => ({
id: n.id,
title: n.title || "Untitled",
type: n.type || "Unknown"
})).sort((a, b) => a.id - b.id);
}
/**
* Get a node by its ID.
*/
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"NodeSelector.js","sources":["../../src/info-panel/NodeSelector.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Node Selector Utility\n * \n * Provides methods to fetch and sort nodes from the canvas\n * for use in the node search/selection feature.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\n\n/**\n * Node entry for dropdown display.\n */\nexport interface NodeListEntry {\n id: number;\n title: string;\n type: string;\n}\n\n/**\n * Node entry with execution order.\n */\nexport interface NodeExecOrderEntry extends NodeListEntry {\n order: number;\n}\n\n/**\n * NodeSelector class.\n * Provides methods to fetch and sort nodes from the canvas.\n */\nexport class NodeSelector {\n /**\n * Get all nodes from the graph.\n */\n getAllNodes(): ComfyNode[] {\n return app?.graph?._nodes ?? [];\n }\n\n /**\n * Get nodes sorted alphabetically by title.\n */\n getNodesSortedByTitle(): NodeListEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a, b) => a.title.localeCompare(b.title));\n }\n\n /**\n * Get nodes sorted by execution order.\n * Only includes nodes that have a valid execution order.\n */\n getNodesSortedByExecOrder(): NodeExecOrderEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown',\n order: (n as any).order ?? -1\n }))\n .filter(n => n.order >= 0)\n .sort((a, b) => a.order - b.order);\n }\n\n /**\n * Get a node by its ID.\n */\n getNodeById(id: number): ComfyNode | null {\n return app?.graph?.getNodeById(id) ?? null;\n }\n\n /**\n * Search nodes by title (case-insensitive partial match).\n */\n searchByTitle(query: string): NodeListEntry[] {\n const lowerQuery = query.toLowerCase();\n return this.getNodesSortedByTitle()\n .filter(n => n.title.toLowerCase().includes(lowerQuery));\n }\n\n /**\n * Get total node count.\n */\n getNodeCount(): number {\n return this.getAllNodes().length;\n }\n}\n"],"names":[],"mappings":"AA+BO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA,EAItB,cAA2B;AAJxB;AAKC,aAAO,gCAAK,UAAL,mBAAY,WAAU,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAyC;AACrC,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,IAAA,EAClB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BAAkD;AAC9C,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,MAChB,OAAQ,EAAU,SAAS;AAAA,IAAA,EAC7B,EACD,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC,EACxB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,IAA8B;AAxCvC;AAyCC,aAAO,gCAAK,UAAL,mBAAY,YAAY,QAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAAgC;AAC1C,UAAM,aAAa,MAAM,YAAA;AACzB,WAAO,KAAK,wBACP,OAAO,CAAA,MAAK,EAAE,MAAM,YAAA,EAAc,SAAS,UAAU,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACnB,WAAO,KAAK,cAAc;AAAA,EAC9B;AACJ;"}
{"version":3,"file":"NodeSelector.js","sources":["../../src/info-panel/NodeSelector.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Node Selector Utility\n * \n * Provides methods to fetch and sort nodes from the canvas\n * for use in the node search/selection feature.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\n\n/**\n * Node entry for dropdown display.\n */\nexport interface NodeListEntry {\n id: number;\n title: string;\n type: string;\n}\n\n/**\n * Node entry with execution order.\n */\nexport interface NodeExecOrderEntry extends NodeListEntry {\n order: number;\n}\n\n/**\n * NodeSelector class.\n * Provides methods to fetch and sort nodes from the canvas.\n */\nexport class NodeSelector {\n /**\n * Get all nodes from the graph.\n */\n getAllNodes(): ComfyNode[] {\n return app?.graph?._nodes ?? [];\n }\n\n /**\n * Get nodes sorted alphabetically by title.\n */\n getNodesSortedByTitle(): NodeListEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a, b) => a.title.localeCompare(b.title));\n }\n\n /**\n * Get nodes sorted by execution order.\n * Only includes nodes that have a valid execution order.\n */\n getNodesSortedByExecOrder(): NodeExecOrderEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown',\n order: (n as any).order ?? -1\n }))\n .filter(n => n.order >= 0)\n .sort((a, b) => a.order - b.order);\n }\n\n /**\n * Get nodes sorted by ID number (ascending).\n */\n getNodesSortedById(): NodeListEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a, b) => a.id - b.id);\n }\n\n /**\n * Get a node by its ID.\n */\n getNodeById(id: number): ComfyNode | null {\n return app?.graph?.getNodeById(id) ?? null;\n }\n\n /**\n * Search nodes by title (case-insensitive partial match).\n */\n searchByTitle(query: string): NodeListEntry[] {\n const lowerQuery = query.toLowerCase();\n return this.getNodesSortedByTitle()\n .filter(n => n.title.toLowerCase().includes(lowerQuery));\n }\n\n /**\n * Get total node count.\n */\n getNodeCount(): number {\n return this.getAllNodes().length;\n }\n}\n"],"names":[],"mappings":"AA+BO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA,EAItB,cAA2B;AAJxB;AAKC,aAAO,gCAAK,UAAL,mBAAY,WAAU,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAyC;AACrC,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,IAAA,EAClB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BAAkD;AAC9C,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,MAChB,OAAQ,EAAU,SAAS;AAAA,IAAA,EAC7B,EACD,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC,EACxB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAsC;AAClC,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,IAAA,EAClB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,IAA8B;AArDvC;AAsDC,aAAO,gCAAK,UAAL,mBAAY,YAAY,QAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAAgC;AAC1C,UAAM,aAAa,MAAM,YAAA;AACzB,WAAO,KAAK,wBACP,OAAO,CAAA,MAAK,EAAE,MAAM,YAAA,EAAc,SAAS,UAAU,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACnB,WAAO,KAAK,cAAc;AAAA,EAC9B;AACJ;"}
+14 -1
View File
@@ -547,7 +547,8 @@ class UIManager {
}
if (info.hoveredNode) {
const nodeContent = [
{ label: "Title", value: info.hoveredNode.title, clickable: "title" }
{ label: "Title", value: info.hoveredNode.title, clickable: "title" },
{ label: "ID", value: `#${info.hoveredNode.id}`, clickable: "id" }
];
if (info.hoveredNode.executionOrder !== void 0) {
nodeContent.push({ label: "Exec Order", value: info.hoveredNode.executionOrder, clickable: "execOrder" });
@@ -653,6 +654,8 @@ class UIManager {
this.showTitleDropdown(row);
} else if (clickableType === "execOrder") {
this.showExecOrderDropdown(row);
} else if (clickableType === "id") {
this.showIdDropdown(row);
}
});
row.addEventListener("mouseenter", () => {
@@ -742,6 +745,16 @@ class UIManager {
if (nodes.length === 0) return;
this.createDropdown(nodes, anchorElement, "execOrder");
}
/**
* Show dropdown with nodes sorted by ID.
* @param anchorElement - Element to anchor the dropdown to
*/
showIdDropdown(anchorElement) {
this.hideDropdown();
const nodes = this.nodeSelector.getNodesSortedById();
if (nodes.length === 0) return;
this.createDropdown(nodes, anchorElement, "id");
}
/**
* Create and show the dropdown.
*/
File diff suppressed because one or more lines are too long
+6
View File
@@ -229,6 +229,12 @@ class PopOutManager {
type: n.type || "Unknown",
order: n.order ?? -1
})).filter((n) => n.order >= 0).sort((a, b) => a.order - b.order);
} else if (sortBy === "id") {
nodeList = nodes.map((n) => ({
id: n.id,
title: n.title || "Untitled",
type: n.type || "Unknown"
})).sort((a, b) => a.id - b.id);
} else {
nodeList = nodes.map((n) => ({
id: n.id,
File diff suppressed because one or more lines are too long
+11
View File
@@ -1082,6 +1082,11 @@
<span class="info-label">Title</span>
<span class="info-value">${escapeHtml(info.hoveredNode.title || 'Untitled')}<span class="dropdown-indicator">▼</span></span>
</div>
${info.hoveredNode.id !== undefined ? `
<div class="info-row clickable-row" onclick="requestNodeDropdown('id', this)">
<span class="info-label">ID</span>
<span class="info-value">#${info.hoveredNode.id}<span class="dropdown-indicator">▼</span></span>
</div>` : ''}
${info.hoveredNode.executionOrder !== undefined ? `
<div class="info-row clickable-row" onclick="requestNodeDropdown('execOrder', this)">
<span class="info-label">Exec Order</span>
@@ -1283,6 +1288,12 @@
<span class="node-title">${escapeHtml(node.title)}</span>
<span class="node-type">${escapeHtml(node.type)}</span>
`;
} else if (sortBy === 'id') {
item.innerHTML = `
<span class="node-order">#${node.id}</span>
<span class="node-title">${escapeHtml(node.title)}</span>
<span class="node-type">${escapeHtml(node.type)}</span>
`;
} else {
item.innerHTML = `
<span class="node-title">${escapeHtml(node.title)}</span>