feat: Add node sorting by ID to the info panel and pop-out viewer.
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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 @@
|
||||
{"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;"}
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user