diff --git a/.eslintrc.js b/.eslintrc.js index 41064fb..d099f97 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,9 +7,11 @@ module.exports = { plugins: ["prettier"], rules: { "class-methods-use-this": "off", + "max-classes-per-file": "off", + "no-console": "off", + "no-param-reassign": "off", "prettier/prettier": "error", curly: ["error", "all"], - "no-console": "off", }, parserOptions: { ecmaVersion: "latest", diff --git a/javascript/dynamic_prompting.js b/javascript/dynamic_prompting.js index 417e429..1f7599d 100644 --- a/javascript/dynamic_prompting.js +++ b/javascript/dynamic_prompting.js @@ -1,4 +1,4 @@ -/* global gradioApp, TreeView, get_uiCurrentTabContent, onUiUpdate, onUiLoaded */ +/* global gradioApp, get_uiCurrentTabContent, onUiUpdate, onUiLoaded */ // prettier-ignore const SDDP_HELP_TEXTS = { "sddp-disable-negative-prompt": "Don't use prompt magic on negative prompts.", @@ -15,6 +15,121 @@ const SDDP_HELP_TEXTS = { "sddp-write-raw-template": "Write template into image metadata.", }; +class SDDPTreeView { + /** + * @constructor + * @property {object} handlers The attached event handlers + * @property {object} data The JSON object that represents the tree structure + * @property {Element} node The DOM element to render the tree in + */ + constructor(data, node) { + this.handlers = {}; + this.node = node; + this.data = data; + this.render(); + } + + /** + * Renders the tree view in the DOM + */ + render = () => { + const container = this.node; + container.innerHTML = ""; + this.data.forEach((item) => container.appendChild(this.renderNode(item))); + [...container.querySelectorAll(".tree-leaf-text,.tree-expando")].forEach( + (node) => node.addEventListener("click", this.handleClickEvent), + ); + }; + + renderNode = (item) => { + const leaf = document.createElement("div"); + const content = document.createElement("div"); + const text = document.createElement("div"); + const expando = document.createElement("div"); + leaf.setAttribute("class", "tree-leaf"); + content.setAttribute("class", "tree-leaf-content"); + text.setAttribute("class", "tree-leaf-text"); + const { children, name, expanded } = item; + text.textContent = name; + expando.setAttribute("class", `tree-expando ${expanded ? "expanded" : ""}`); + expando.textContent = expanded ? "-" : "+"; + content.appendChild(expando); + content.appendChild(text); + leaf.appendChild(content); + if (children?.length > 0) { + const childrenDiv = document.createElement("div"); + childrenDiv.setAttribute("class", "tree-child-leaves"); + children.forEach((child) => { + childrenDiv.appendChild(this.renderNode(child)); + }); + if (!expanded) { + childrenDiv.classList.add("hidden"); + } + leaf.appendChild(childrenDiv); + } else { + expando.classList.add("hidden"); + content.setAttribute("data-item", JSON.stringify(item)); + } + return leaf; + }; + + handleClickEvent = (event) => { + const parent = (event.target || event.currentTarget).parentNode; + const leaves = parent.parentNode.querySelector(".tree-child-leaves"); + if (leaves) { + this.setSubtreeVisibility( + parent, + leaves, + leaves.classList.contains("hidden"), + ); + } else { + this.emit("select", { + target: event, + data: JSON.parse(parent.getAttribute("data-item")), + }); + } + }; + + /** + * Expands/collapses by the expando or the leaf text + * @param {Element} node The parent node that contains the leaves + * @param {Element} leaves The leaves wrapper element + * @param {boolean} visible Expand or collapse? + * @param {boolean} skipEmit Skip emitting the event? + */ + setSubtreeVisibility(node, leaves, visible, skipEmit = false) { + leaves.classList.toggle("hidden", !visible); + node.querySelector(".tree-expando").textContent = visible ? "+" : "-"; + if (skipEmit) { + return; + } + this.emit(visible ? "expand" : "collapse", { + target: node, + leaves, + }); + } + + on(name, callback, context = null) { + const handlers = this.handlers[name] || []; + handlers.push({ callback, context }); + this.handlers[name] = handlers; + } + + off(name, callback) { + this.handlers[name] = (this.handlers[name] || []).filter( + (handle) => handle.callback !== callback, + ); + } + + emit(name, ...args) { + (this.handlers[name] || []).forEach((handle) => { + window.setTimeout(() => { + handle.callback.apply(handle.context, args); + }, 0); + }); + } +} + class SDDP_UI { constructor() { this.helpTextsConfigured = false; @@ -86,7 +201,7 @@ class SDDP_UI { if (!this.treeView) { const treeDiv = gradioApp().querySelector("#sddp-wildcard-tree"); if (treeDiv) { - treeView = new TreeView(content, treeDiv); + treeView = new SDDPTreeView(content, treeDiv); treeView.on("select", this.onSelectNode.bind(this), null); this.treeView = treeView; } diff --git a/javascript/treeview.js b/javascript/treeview.js deleted file mode 100644 index 5efd481..0000000 --- a/javascript/treeview.js +++ /dev/null @@ -1,263 +0,0 @@ -(function (define) { - 'use strict'; - - (function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.TreeView = factory(); - } - }(window, function () { - return (function () { - - /** List of events supported by the tree view */ - var events = [ - 'expand', - 'expandAll', - 'collapse', - 'collapseAll', - 'select' - ]; - - /** - * A utilite function to check to see if something is a DOM object - * @param {object} Object to test - * @returns {boolean} If the object is a DOM object - */ - function isDOMElement(obj) { - try { - return obj instanceof HTMLElement; - } catch (e) { - // Some browsers don't support using the HTMLElement so some extra - // checks are needed. - return typeof obj === 'object' && obj.nodeType === 1 && typeof obj.style === 'object' && typeof obj.ownerDocument === 'object'; - } - } - - /** - * A forEach that will work with a NodeList and generic Arrays - * @param {array|NodeList} arr The array to iterate over - * @param {function} callback Function that executes for each element. First parameter is element, second is index - * @param {object} The context to execute callback with - */ - function forEach(arr, callback, scope) { - var i, len = arr.length; - for (i = 0; i < len; i += 1) { - callback.call(scope, arr[i], i); - } - } - - /** - * Emit an event from the tree view - * @param {string} name The name of the event to emit - */ - function emit(instance, name) { - var args = [].slice.call(arguments, 2); - if (events.indexOf(name) > -1) { - if (instance.handlers[name] && instance.handlers[name] instanceof Array) { - forEach(instance.handlers[name], function (handle) { - window.setTimeout(function () { - handle.callback.apply(handle.context, args); - }, 0); - }); - } - } else { - throw new Error(name + ' event cannot be found on TreeView.'); - } - } - - /** - * Renders the tree view in the DOM - */ - function render(self) { - var container = isDOMElement(self.node) ? self.node : document.getElementById(self.node); - var leaves = [], click; - var renderLeaf = function (item) { - var leaf = document.createElement('div'); - var content = document.createElement('div'); - var text = document.createElement('div'); - var expando = document.createElement('div'); - - leaf.setAttribute('class', 'tree-leaf'); - content.setAttribute('class', 'tree-leaf-content'); - content.setAttribute('data-item', JSON.stringify(item)); - text.setAttribute('class', 'tree-leaf-text'); - text.textContent = item.name; - expando.setAttribute('class', 'tree-expando ' + (item.expanded ? 'expanded' : '')); - expando.textContent = item.expanded ? '-' : '+'; - content.appendChild(expando); - content.appendChild(text); - leaf.appendChild(content); - if (item.children && item.children.length > 0) { - var children = document.createElement('div'); - children.setAttribute('class', 'tree-child-leaves'); - forEach(item.children, function (child) { - var childLeaf = renderLeaf(child); - children.appendChild(childLeaf); - }); - if (!item.expanded) { - children.classList.add('hidden'); - } - leaf.appendChild(children); - } else { - expando.classList.add('hidden'); - } - return leaf; - }; - - forEach(self.data, function (item) { - leaves.push(renderLeaf.call(self, item)); - }); - container.innerHTML = leaves.map(function (leaf) { - return leaf.outerHTML; - }).join(''); - - click = function (e) { - var parent = (e.target || e.currentTarget).parentNode; - var data = JSON.parse(parent.getAttribute('data-item')); - var leaves = parent.parentNode.querySelector('.tree-child-leaves'); - if (leaves) { - if (leaves.classList.contains('hidden')) { - self.expand(parent, leaves); - } else { - self.collapse(parent, leaves); - } - } else { - emit(self, 'select', { - target: e, - data: data - }); - } - }; - - forEach(container.querySelectorAll('.tree-leaf-text'), function (node) { - node.onclick = click; - }); - forEach(container.querySelectorAll('.tree-expando'), function (node) { - node.onclick = click; - }); - } - - /** - * @constructor - * @property {object} handlers The attached event handlers - * @property {object} data The JSON object that represents the tree structure - * @property {DOMElement} node The DOM element to render the tree in - */ - function TreeView(data, node) { - this.handlers = {}; - this.node = node; - this.data = data; - render(this); - } - - TreeView.prototype.render = function () { - render(this); - }; - - /** - * Expands a leaflet by the expando or the leaf text - * @param {DOMElement} node The parent node that contains the leaves - * @param {DOMElement} leaves The leaves wrapper element - */ - TreeView.prototype.expand = function (node, leaves, skipEmit) { - var expando = node.querySelector('.tree-expando'); - expando.textContent = '-'; - leaves.classList.remove('hidden'); - if (skipEmit) { return; } - emit(this, 'expand', { - target: node, - leaves: leaves - }); - }; - - TreeView.prototype.expandAll = function () { - var self = this; - var nodes = document.getElementById(self.node).querySelectorAll('.tree-expando'); - forEach(nodes, function (node) { - var parent = node.parentNode; - var leaves = parent.parentNode.querySelector('.tree-child-leaves'); - if (parent && leaves && parent.hasAttribute('data-item')) { - self.expand(parent, leaves, true); - } - }); - emit(this, 'expandAll', {}); - }; - - /** - * Collapses a leaflet by the expando or the leaf text - * @param {DOMElement} node The parent node that contains the leaves - * @param {DOMElement} leaves The leaves wrapper element - */ - TreeView.prototype.collapse = function (node, leaves, skipEmit) { - var expando = node.querySelector('.tree-expando'); - expando.textContent = '+'; - leaves.classList.add('hidden'); - if (skipEmit) { return; } - emit(this, 'collapse', { - target: node, - leaves: leaves - }); - }; - - /** - */ - TreeView.prototype.collapseAll = function () { - var self = this; - var nodes = document.getElementById(self.node).querySelectorAll('.tree-expando'); - forEach(nodes, function (node) { - var parent = node.parentNode; - var leaves = parent.parentNode.querySelector('.tree-child-leaves'); - if (parent && leaves && parent.hasAttribute('data-item')) { - self.collapse(parent, leaves, true); - } - }); - emit(this, 'collapseAll', {}); - }; - - /** - * Attach an event handler to the tree view - * @param {string} name Name of the event to attach - * @param {function} callback The callback to execute on the event - * @param {object} scope The context to call the callback with - */ - TreeView.prototype.on = function (name, callback, scope) { - if (events.indexOf(name) > -1) { - if (!this.handlers[name]) { - this.handlers[name] = []; - } - this.handlers[name].push({ - callback: callback, - context: scope - }); - } else { - throw new Error(name + ' is not supported by TreeView.'); - } - }; - - /** - * Deattach an event handler from the tree view - * @param {string} name Name of the event to deattach - * @param {function} callback The function to deattach - */ - TreeView.prototype.off = function (name, callback) { - var index, found = false; - if (this.handlers[name] instanceof Array) { - this.handlers[name].forEach(function (handle, i) { - index = i; - if (handle.callback === callback && !found) { - found = true; - } - }); - if (found) { - this.handlers[name].splice(index, 1); - } - } - }; - - return TreeView; - }()); - })); - }(window.define));