diff --git a/js/custom-nodes-downloader.js b/js/custom-nodes-downloader.js
deleted file mode 100644
index 26f8966b..00000000
--- a/js/custom-nodes-downloader.js
+++ /dev/null
@@ -1,951 +0,0 @@
-import { app } from "../../scripts/app.js";
-import { api } from "../../scripts/api.js"
-import { ComfyDialog, $el } from "../../scripts/ui.js";
-import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js";
-
-
-async function getCustomNodes() {
- var mode = manager_instance.datasrc_combo.value;
-
- var skip_update = "";
- if(manager_instance.update_check_checkbox.checked)
- skip_update = "&skip_update=true";
-
- const response = await api.fetchApi(`/customnode/getlist?mode=${mode}${skip_update}`);
-
- const data = await response.json();
- return data;
-}
-
-async function getCustomnodeMappings() {
- var mode = manager_instance.datasrc_combo.value;
-
- const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`);
-
- const data = await response.json();
- return data;
-}
-
-async function getConflictMappings() {
- var mode = manager_instance.datasrc_combo.value;
-
- const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`);
-
- const data = await response.json();
-
- let node_to_extensions_map = {};
-
- for(let k in data) {
- for(let i in data[k][0]) {
- let node = data[k][0][i];
- let l = node_to_extensions_map[node];
- if(!l) {
- l = [];
- node_to_extensions_map[node] = l;
- }
- l.push(k);
- }
- }
-
- let conflict_map = {};
- for(let node in node_to_extensions_map) {
- if(node_to_extensions_map[node].length > 1) {
- for(let i in node_to_extensions_map[node]) {
- let extension = node_to_extensions_map[node][i];
- let l = conflict_map[extension];
-
- if(!l) {
- l = [];
- conflict_map[extension] = l;
- }
-
- for(let j in node_to_extensions_map[node]) {
- let extension2 = node_to_extensions_map[node][j];
- if(extension != extension2)
- l.push([node, extension2]);
- }
- }
- }
- }
-
- return conflict_map;
-}
-
-async function getUnresolvedNodesInComponent() {
- try {
- var mode = manager_instance.datasrc_combo.value;
-
- const response = await api.fetchApi(`/component/get_unresolved`);
-
- const data = await response.json();
- return data.nodes;
- }
- catch {
- return [];
- }
-}
-
-export class CustomNodesInstaller extends ComfyDialog {
- static instance = null;
-
- install_buttons = [];
- message_box = null;
- data = null;
-
- static ShowMode = {
- NORMAL: 0,
- MISSING_NODES: 1,
- UPDATE: 2,
- };
-
- clear() {
- this.install_buttons = [];
- this.message_box = null;
- this.data = null;
- }
-
- constructor(app, manager_dialog) {
- super();
- this.manager_dialog = manager_dialog;
- this.search_keyword = '';
- this.element = $el("div.comfy-modal", { parent: document.body }, []);
-
- this.currentSortProperty = ''; // The property currently being sorted
- this.currentSortAscending = true; // The direction of the current sort
- }
-
- startInstall(target) {
- const self = CustomNodesInstaller.instance;
-
- self.updateMessage(`
Installing '${target.title}'`);
- }
-
- disableButtons() {
- for(let i in this.install_buttons) {
- this.install_buttons[i].disabled = true;
- this.install_buttons[i].style.backgroundColor = 'gray';
- }
- }
-
- apply_searchbox(data) {
- let keyword = this.search_box.value.toLowerCase();
- for(let i in this.grid_rows) {
- let data = this.grid_rows[i].data;
- let content = data.author.toLowerCase() + data.description.toLowerCase() + data.title.toLowerCase() + data.reference.toLowerCase();
-
- if(this.filter && this.filter != '*') {
- if(this.filter == 'True' && (data.installed == 'Update' || data.installed == 'Fail')) {
- this.grid_rows[i].control.style.display = null;
- }
- else if(this.filter != data.installed) {
- this.grid_rows[i].control.style.display = 'none';
- continue;
- }
- }
-
- if(keyword == "")
- this.grid_rows[i].control.style.display = null;
- else if(content.includes(keyword)) {
- this.grid_rows[i].control.style.display = null;
- }
- else {
- this.grid_rows[i].control.style.display = 'none';
- }
- }
- }
-
- async filter_missing_node(data) {
- const mappings = await getCustomnodeMappings();
-
- // build regex->url map
- const regex_to_url = [];
- for (let i in data) {
- if(data[i]['nodename_pattern']) {
- let item = {regex: new RegExp(data[i].nodename_pattern), url: data[i].files[0]};
- regex_to_url.push(item);
- }
- }
-
- // build name->url map
- const name_to_urls = {};
- for (const url in mappings) {
- const names = mappings[url];
-
- for(const name in names[0]) {
- let v = name_to_urls[names[0][name]];
- if(v == undefined) {
- v = [];
- name_to_urls[names[0][name]] = v;
- }
- v.push(url);
- }
- }
-
- const registered_nodes = new Set();
- for (let i in LiteGraph.registered_node_types) {
- registered_nodes.add(LiteGraph.registered_node_types[i].type);
- }
-
- const missing_nodes = new Set();
- const workflow = app.graph.serialize();
- const group_nodes = workflow.extra && workflow.extra.groupNodes ? workflow.extra.groupNodes : [];
- let nodes = workflow.nodes;
-
- for (let i in group_nodes) {
- let group_node = group_nodes[i];
- nodes = nodes.concat(group_node.nodes);
- }
-
- for (let i in nodes) {
- const node_type = nodes[i].type;
- if(node_type.startsWith('workflow/'))
- continue;
-
- if (!registered_nodes.has(node_type)) {
- const urls = name_to_urls[node_type.trim()];
- if(urls)
- urls.forEach(url => {
- missing_nodes.add(url);
- });
- else {
- for(let j in regex_to_url) {
- if(regex_to_url[j].regex.test(node_type)) {
- missing_nodes.add(regex_to_url[j].url);
- }
- }
- }
- }
- }
-
- let unresolved_nodes = await getUnresolvedNodesInComponent();
- for (let i in unresolved_nodes) {
- let node_type = unresolved_nodes[i];
- const url = name_to_urls[node_type];
- if(url)
- missing_nodes.add(url);
- }
-
- return data.filter(node => node.files.some(file => missing_nodes.has(file)));
- }
-
- async invalidateControl() {
- this.clear();
-
- // splash
- while (this.element.children.length) {
- this.element.removeChild(this.element.children[0]);
- }
-
- const msg = $el('div', {id:'custom-message'},
- [$el('br'),
- 'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
- $el('br'),
- 'NOTE: Update only checks for extensions that have been fetched.',
- $el('br')]);
- msg.style.height = '100px';
- msg.style.verticalAlign = 'middle';
- msg.style.color = "var(--fg-color)";
-
- this.element.appendChild(msg);
-
- // invalidate
- let data = await getCustomNodes();
- this.data = data.custom_nodes;
- this.channel = data.channel;
-
- this.conflict_mappings = await getConflictMappings();
-
- if(this.show_mode == CustomNodesInstaller.ShowMode.MISSING_NODES)
- this.data = await this.filter_missing_node(this.data);
-
- this.element.removeChild(msg);
-
- while (this.element.children.length) {
- this.element.removeChild(this.element.children[0]);
- }
-
- this.createHeaderControls();
- await this.createGrid();
- this.apply_searchbox(this.data);
- this.createBottomControls();
- }
-
- updateMessage(msg, btn_id) {
- this.message_box.innerHTML = msg;
- if(btn_id) {
- const rebootButton = document.getElementById(btn_id);
- const self = this;
- rebootButton.addEventListener("click",
- function() {
- if(rebootAPI()) {
- self.close();
- self.manager_dialog.close();
- }
- });
- console.log(rebootButton);
- }
- }
-
- invalidate_checks(is_checked, install_state) {
- if(is_checked) {
- for(let i in this.grid_rows) {
- let data = this.grid_rows[i].data;
- let checkbox = this.grid_rows[i].checkbox;
- let buttons = this.grid_rows[i].buttons;
-
- checkbox.disabled = data.installed != install_state;
-
- if(checkbox.disabled) {
- for(let j in buttons) {
- buttons[j].style.display = 'none';
- }
- }
- else {
- for(let j in buttons) {
- buttons[j].style.display = null;
- }
- }
- }
-
- this.checkbox_all.disabled = false;
- }
- else {
- for(let i in this.grid_rows) {
- let checkbox = this.grid_rows[i].checkbox;
- if(checkbox.check)
- return; // do nothing
- }
-
- // every checkbox is unchecked -> enable all checkbox
- for(let i in this.grid_rows) {
- let checkbox = this.grid_rows[i].checkbox;
- let buttons = this.grid_rows[i].buttons;
- checkbox.disabled = false;
-
- for(let j in buttons) {
- buttons[j].style.display = null;
- }
- }
-
- this.checkbox_all.checked = false;
- this.checkbox_all.disabled = true;
- }
- }
-
- check_all(is_checked) {
- if(is_checked) {
- // lookup first checked item's state
- let check_state = null;
- for(let i in this.grid_rows) {
- let checkbox = this.grid_rows[i].checkbox;
- if(checkbox.checked) {
- check_state = this.grid_rows[i].data.installed;
- }
- }
-
- if(check_state == null)
- return;
-
- // check only same state items
- for(let i in this.grid_rows) {
- let checkbox = this.grid_rows[i].checkbox;
- if(this.grid_rows[i].data.installed == check_state)
- checkbox.checked = true;
- }
- }
- else {
- // uncheck all
- for(let i in this.grid_rows) {
- let checkbox = this.grid_rows[i].checkbox;
- let buttons = this.grid_rows[i].buttons;
- checkbox.checked = false;
- checkbox.disabled = false;
-
- for(let j in buttons) {
- buttons[j].style.display = null;
- }
- }
-
- this.checkbox_all.disabled = true;
- }
- }
-
- sortData(property, ascending = true) {
- this.data.sort((a, b) => {
- // Check if either value is -1 and handle accordingly
- if (a[property] === -1) return 1; // Always put a at the end if its value is -1
- if (b[property] === -1) return -1; // Always put b at the end if its value is -1
- // And be careful here, (-1<'2024-01-01') and (-1>'2024-01-01') are both false! So I handle -1 seperately.
- if (a[property] < b[property]) return ascending ? -1 : 1;
- if (a[property] > b[property]) return ascending ? 1 : -1;
- return 0;
- });
- }
-
- resetHeaderStyles() {
- const headers = ['th_author', 'th_title', 'th_stars', 'th_last_update']; // Add the IDs of all your sortable headers here
- headers.forEach(headerId => {
- const header = this.element.querySelector(`#${headerId}`);
- if (header) {
- header.style.backgroundColor = ''; // Reset to default background color
- // Add other style resets if necessary
- }
- });
- }
-
- toggleSort(property) {
- // If currently sorted by this property, toggle the direction; else, sort ascending
- if (this.currentSortProperty === property) {
- this.currentSortAscending = !this.currentSortAscending;
- } else {
- this.currentSortAscending = false;
- }
- this.currentSortProperty = property;
-
- this.resetHeaderStyles(); // Reset styles of all sortable headers
-
- // Determine the ID of the header based on the property
- let headerId = '';
- if (property === 'stars') {
- headerId = 'th_stars';
- } else if (property === 'last_update') {
- headerId = 'th_last_update';
- } else if (property === 'author') {
- headerId = 'th_author';
- } else if (property === 'title') {
- headerId = 'th_title';
- }
-
- // If we have a valid headerId, change its style to indicate it's the active sort column
- if (headerId) {
- const activeHeader = this.element.querySelector(`#${headerId}`);
- if (activeHeader) {
- activeHeader.style.backgroundColor = '#222';
- // Slightly brighter. Add other style changes if necessary.
- }
- }
-
- // Call sortData with the current property and direction
- this.sortData(property, this.currentSortAscending);
-
- // Refresh the grid to display sorted data
- this.createGrid();
- this.apply_searchbox(this.data);
- }
-
- async createGrid() {
- // Remove existing table if present
- var grid = this.element.querySelector('#custom-nodes-grid');
- var panel;
- let self = this;
- if (grid) {
- grid.querySelector('tbody').remove();
- panel = grid.parentNode;
- } else {
- grid = document.createElement('table');
- grid.setAttribute('id', 'custom-nodes-grid');
-
- this.grid_rows = {};
-
- var thead = document.createElement('thead');
-
- var headerRow = document.createElement('tr');
- thead.style.position = "sticky";
- thead.style.top = "0px";
- thead.style.borderCollapse = "collapse";
- thead.style.tableLayout = "fixed";
-
- var header0 = document.createElement('th');
- header0.style.width = "20px";
- this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]);
- header0.appendChild(this.checkbox_all);
- this.checkbox_all.checked = false;
- this.checkbox_all.disabled = true;
- this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); });
-
- var header1 = document.createElement('th');
- header1.innerHTML = ' ID ';
- header1.style.width = "20px";
-
- var header2 = document.createElement('th');
- header2.innerHTML = 'Author';
- header2.style.width = "150px";
- header2.style.cursor = 'pointer';
- header2.setAttribute('id', 'th_author');
- header2.onclick = () => this.toggleSort('author');
-
- var header3 = document.createElement('th');
- header3.innerHTML = 'Name';
- header3.style.width = "20%";
- header3.style.cursor = 'pointer';
- header3.setAttribute('id', 'th_title');
- header3.onclick = () => this.toggleSort('title');
-
- var header4 = document.createElement('th');
- header4.innerHTML = 'Description';
- header4.style.width = "60%";
- // header4.classList.add('expandable-column');
-
- var header5 = document.createElement('th');
- header5.innerHTML = ' ★ ';
- header5.style.width = "130px";
- header5.setAttribute('id', 'th_stars');
- header5.style.cursor = 'pointer';
- header5.onclick = () => this.toggleSort('stars');
-
- var header6 = document.createElement('th');
- header6.innerHTML = 'Last Update';
- header6.style.width = "130px";
- header6.setAttribute('id', 'th_last_update');
- header6.style.cursor = 'pointer';
- header6.onclick = () => this.toggleSort('last_update');
-
- var header7 = document.createElement('th');
- header7.innerHTML = 'Install';
- header7.style.width = "130px";
-
- header0.style.position = "sticky";
- header0.style.top = "0px";
- header1.style.position = "sticky";
- header1.style.top = "0px";
- header2.style.position = "sticky";
- header2.style.top = "0px";
- header3.style.position = "sticky";
- header3.style.top = "0px";
- header4.style.position = "sticky";
- header4.style.top = "0px";
- header5.style.position = "sticky";
- header5.style.top = "0px";
- header6.style.position = "sticky";
- header6.style.top = "0px";
- header7.style.position = "sticky";
- header7.style.top = "0px";
-
- thead.appendChild(headerRow);
- headerRow.appendChild(header0);
- headerRow.appendChild(header1);
- headerRow.appendChild(header2);
- headerRow.appendChild(header3);
- headerRow.appendChild(header4);
- headerRow.appendChild(header5);
- headerRow.appendChild(header6);
- headerRow.appendChild(header7);
-
- headerRow.style.backgroundColor = "Black";
- headerRow.style.color = "White";
- headerRow.style.textAlign = "center";
- headerRow.style.width = "100%";
- headerRow.style.padding = "0";
-
- grid.appendChild(thead);
-
- panel = document.createElement('div');
- panel.style.width = "100%";
- panel.appendChild(grid);
- this.element.appendChild(panel);
- }
- var tbody = document.createElement('tbody');
- grid.appendChild(tbody);
-
- if(this.data)
- for (var i = 0; i < this.data.length; i++) {
- const data = this.data[i];
- let dataRow = document.createElement('tr');
-
- let data0 = document.createElement('td');
- let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]);
- data0.appendChild(checkbox);
- checkbox.checked = false;
- checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.installed); });
-
- var data1 = document.createElement('td');
- data1.style.textAlign = "center";
- data1.innerHTML = i+1;
-
- var data2 = document.createElement('td');
- data2.style.maxWidth = "120px";
- data2.className = "cm-node-author"
- data2.textContent = ` ${data.author}`;
-
- if(data.trust) {
- data2.innerHTML = `✅${data.author}`;
- }
- else {
- data2.textContent = ` ${data.author}`;
- }
-
- data2.style.whiteSpace = "nowrap";
- data2.style.overflow = "hidden";
- data2.style.textOverflow = "ellipsis";
-
- var data3 = document.createElement('td');
- data3.style.maxWidth = "200px";
- data3.style.wordWrap = "break-word";
- data3.className = "cm-node-name"
- data3.innerHTML = ` ${data.title}`;
- if(data.installed == 'Fail')
- data3.innerHTML = ' (IMPORT FAILED)' + data3.innerHTML;
-
- var data4 = document.createElement('td');
- data4.innerHTML = data.description;
- data4.className = "cm-node-desc"
-
- let conflicts = this.conflict_mappings[data.files[0]];
- if(conflicts) {
- let buf = '
Conflicted Nodes:
';
- for(let k in conflicts) {
- let node_name = conflicts[k][0];
-
- let extension_name = conflicts[k][1].split('/').pop();
- if(extension_name.endsWith('/')) {
- extension_name = extension_name.slice(0, -1);
- }
- if(node_name.endsWith('.git')) {
- extension_name = extension_name.slice(0, -4);
- }
-
- buf += `${node_name} [${extension_name}], `;
- }
-
- if(buf.endsWith(', ')) {
- buf = buf.slice(0, -2);
- }
- buf += "