diff --git a/LICENSE b/LICENSE index 4f7f220..acf3180 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Nuked +Copyright (c) 2023 pythongosssss Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index de5ec93..dedd83d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ + + # ComfyUI-N-Sidebar -A simple sidebar for your ConfyUI! +A simple sidebar for ComfyUI. +For what i know nobody did it, so i did it. +Maybe you don't need it. I need it >.< + +# Installation + +1. Clone the repository: +`git clone https://github.com/Nuked88/ComfyUI-N-Sidebar.git` +to your ComfyUI `custom_nodes` directory +2. Enjoy! + + +# Uninstall + - Delete the `ComfyUI-N-Sidebar` folder in `custom_nodes` + + +# Update +1. Navigate to the cloned repo e.g. `custom_nodes/ComfyUI-N-Sidebar` +2. `git pull` + +# Features + + +### 🖱️Drag and Drop Nodes🖱️ +![Drag and Drop Nodes](./images/dd.gif) + + +### 📌Pin Your Favorite Node📌 +![Pin Your Favorite Node](./images/pin.gif) + +### 🔍Search within your nodes📄 +![Search within your nodes](./images/search_nodes.gif) + +### 🔍Search within categories📂 +![Search within categories](./images/search_categories.gif) + +### ➕Expand/Collapse Categories/Sidebar➖ +![Expand/Collapse Categories and Sidebar](./images/expand_collapse.gif) + +## Contributing + +Feel free to contribute to this project by reporting issues or suggesting improvements. Open an issue or submit a pull request on the GitHub repository. + +## Donations + +If you'd like to support the project, consider making a donation ❤️ +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C0AJECJ) + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0df60da --- /dev/null +++ b/__init__.py @@ -0,0 +1,6 @@ +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} + + + +WEB_DIRECTORY = "./js" \ No newline at end of file diff --git a/images/dd.gif b/images/dd.gif new file mode 100644 index 0000000..802398b Binary files /dev/null and b/images/dd.gif differ diff --git a/images/expand_collapse.gif b/images/expand_collapse.gif new file mode 100644 index 0000000..d4d0710 Binary files /dev/null and b/images/expand_collapse.gif differ diff --git a/images/pin.gif b/images/pin.gif new file mode 100644 index 0000000..bd801c4 Binary files /dev/null and b/images/pin.gif differ diff --git a/images/search_categories.gif b/images/search_categories.gif new file mode 100644 index 0000000..b595483 Binary files /dev/null and b/images/search_categories.gif differ diff --git a/images/search_nodes.gif b/images/search_nodes.gif new file mode 100644 index 0000000..50f7bcd Binary files /dev/null and b/images/search_nodes.gif differ diff --git a/js/sidebar_node.js b/js/sidebar_node.js new file mode 100644 index 0000000..553d02d --- /dev/null +++ b/js/sidebar_node.js @@ -0,0 +1,903 @@ +import { $el } from "../../../scripts/ui.js"; + +let categorySearchToggle = false; +function addSidebarStyles() { + const sidebarStyle = ` + .litegraph .dialog { + z-index: 100 !important; + } + + .sidebar { + position: absolute; + top: 0; + left: -250px; + width: fit-content; + height: calc(100% - 19px); + + color: white; + transition: left 0.3s ease; + z-index: 2; + overflow: hidden; + + padding-top: 19px; + left: 0; + user-select: none; + + } + + .sidebar ul { + list-style-type: none; + + border-bottom: 6px solid #252525; + background: #222; + padding-left: 5px; + padding-right: 5px; + } + .sidebar li { + padding: 10px; + cursor: pointer; + user-select: none; + } + +.content_sidebar { + background-color: #353535; /*#3333337d;*/ + overflow-y: auto; + overflow-x: hidden; + height: 100%; + float:left; + backdrop-filter: blur(5px); +} + +.dragHandle { + position: relative; + float: left; + right: 0; + top: 0; + height: 100%; + width: 10px; + cursor: ew-resize; + background: rgb(62,62,62); +background: linear-gradient(90deg, rgb(62 62 62 / 46%) 0%, rgb(39 39 39 / 47%) 50%, rgb(28 28 28 / 31%) 100%); +} +#searchInput { + box-sizing: border-box; + width: 100%; + border-radius: 5px; + padding: 10px; + border: none; + user-select: none; + background: #222; + color: #fff; + +} +.sidebar-header { + position: absolute; + width: calc(100% - 49px); + margin-top: 5px; + margin-bottom: 10px; + margin-left: 10px; + + z-index: 400; + +} +.clearIcon,.searchCategoryIcon{ + position: absolute; + padding: 5px; + right: 30px; + color: #fff; + font-size: x-large; + cursor: pointer; + opacity: 0.5; + user-select: none; +} + + +.clearIcon:hover{ + opacity: 1.0; +} + +.searchCategoryIcon{ + right: 0px; + padding: 3px; + font-size: larger; + background: #636363; + color: #fff; + margin: 4px; + border-radius: 5px; + width: 18px; + text-align: center; + top: 0; + + +} + +.searchCategoryIcon:hover{ + opacity: 0.5; +} + +.sidebarCategory, #sidebarBookmarks{ + list-style-type: none; + font-family: 'Open Sans',sans-serif; + text-transform: capitalize; + margin: 2px; + background-color: #222; + border-radius: 9px; + padding-top: 11px; + font-size: 15px; +} +.sidebarCategory:hover{ + background-color: #232323; +} +.sidebarItem:hover{ + background-color: #3a3a3a; +} +.sidebarItem { + list-style-type: none; + font-family: 'Open Sans',sans-serif; + text-transform: capitalize; + margin: 2px; + background: #353535; + border-radius: 8px; + + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: calc(100% - 39px); +} + + +.content_sidebar::-webkit-scrollbar { + margin-top: 0.5rem; + height: 1rem; + width: .5rem; + top: 10px; +} + +.content_sidebar::-webkit-scrollbar:horizontal { + height: .5rem; + width: 1rem +} + +.content_sidebar::-webkit-scrollbar-track { + background-color: transparent; + border-radius: 9999px +} + +.content_sidebar::-webkit-scrollbar-thumb { + --tw-border-opacity: 1; + background-color: hsla(0,0%,50%,.8); + border-color: rgba(255,255,255,0,0,0); + border-radius: 9999px; + border-width: 1px +} + +.content_sidebar::-webkit-scrollbar-thumb:hover { + --tw-bg-opacity: 1; + background-color: rgba(150,150,150,var(--tw-bg-opacity)) +} +#spacer { + height: 45px; +} + +#switch_sidebar { + position: relative; + float: left; + left: 0; + margin-left: -0.6rem; + cursor: pointer; + user-select: none; + padding: 2px; + font-size: 20px; + background-color: #353535; + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; + +} +#searchInput.closed { + display: none; +} +#content_sidebar.closed { + width: 0 !important; +} +.searchCategoryIcon.closed { + display: none; +} +.clearIcon.closed { + display: none; +} + +.sidebarCategory .pinButton { + background-color: transparent; + border: 0; + position: absolute; + right: 0; +} + +#sidebarBookmarks .pinButton { + + background-color: transparent; + border: 0; + + position: absolute; + right: 0; +} +.pinned{ + fill: #999 !important; + opacity: 1 !important; +} +.svg_class{ + width: 24px; + height: 24px; + fill: #4e4e4e; + cursor: pointer; + /* hacky fix */ + margin-top: -5px; + margin-left: -25px; +} + +.pin_normal{ + opacity: 0.5; +} +.pin_normal:hover{ + opacity: 1; +} +.svg_class:hover{ + fill: #4e4e4e; + +} + + +#sidebarBookmarks .sidebarItem { + margin-left: 10px; + margin-right: 29px; + +} +.sb_label { + font-family: 'Open Sans',sans-serif; + position: relative; + margin: 5px; + font-weight: bold; + + background: #1e1e1e; + /* border: 2px solid; */ + border-radius: 3px; + padding-left: 6px; + padding-right: 6px; + display: block; + width: calc(100% - 20px); + text-align: center; + user-select: none; +} + +.expand_node, .pin_node{ + position: absolute; + right: 5px; + background: transparent; + border: 0; + +} +.expand_node svg , .pin_node svg { + + width: 20px; + height: 20px; + background: transparent; + fill: white; + cursor: pointer; +} + + + + + `; + + const styleElement = $el("style", { + parent: document.head, + textContent: sidebarStyle + }); +} + +function getCookie(name) { + const cookies = document.cookie.split(';'); + for (const cookie of cookies) { + const [cookieName, cookieValue] = cookie.trim().split('='); + if (cookieName === name) { + return cookieValue; + } + } + return null; +} + +function setCookie(name, value, days) { + let expires = ''; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = '; expires=' + date.toUTCString(); + } + document.cookie = name + '=' + value + expires + '; path=/'; +} + +function sidebarAddNode(name, text, x, y) { + const node = LiteGraph.createNode(name, text) + if (node) { + const pos = + [ + x, + y + ] + node.pos = pos; + app.graph.add(node) + } +} + +function saveSidebarWidth(sideb) { + const width = sideb.style.width; + + + document.cookie = setCookie("sidebarWidth", width, 3000); +} + + +function restoreSidebarWidth() { + let width_sidebar = "auto"; + let cookieValue = getCookie("sidebarWidth"); + if (cookieValue) { + + width_sidebar = cookieValue; + } + return width_sidebar; +} + + +function savePinnedItems(pinnedItems) { + const pinnedItemsString = JSON.stringify(pinnedItems); + setCookie('pinnedItems', pinnedItemsString, 9999); +} + + +function loadPinnedItems() { + const pinnedItemsString = getCookie('pinnedItems'); + if (pinnedItemsString) { + return JSON.parse(pinnedItemsString); + } + return []; +} + + +function pinItem(itemId) { + + const item = document.getElementById(itemId); + const sidebarBookmarks = document.getElementById("sidebarBookmarks"); + const pinnedItems = loadPinnedItems(); + if (!pinnedItems.includes(itemId)) { + let addedItem = sidebarBookmarks.appendChild(item.cloneNode(true)); + + addedItem.lastChild.lastChild.childNodes[1].classList.add("pinned"); + pinnedItems.push(itemId); + savePinnedItems(pinnedItems); + } + +} + + + +function unpinItem(itemId) { + const item = document.getElementById(itemId); + const sidebarBookmarks = document.getElementById("sidebarBookmarks"); + const pinnedItems = loadPinnedItems(); + if (pinnedItems.includes(itemId)) { + sidebarBookmarks.removeChild(item); + pinnedItems.splice(pinnedItems.indexOf(itemId), 1); + savePinnedItems(pinnedItems); + } +} + + +function removePinnedClass(itemId) { + + const sidebarItems = document.querySelectorAll(".sidebarItem"); + + + sidebarItems.forEach(function (item) { + + if (item.id === itemId) { + + const path = item.querySelector("path"); + if (path) { + path.classList.remove("pinned"); + } + } + }); +} + + +function loadPinnedItemsAndAddToBookmarks() { + const pinnedItems = loadPinnedItems(); + + + const sidebarBookmarks = document.getElementById("sidebarBookmarks"); + pinnedItems.forEach(itemId => { + pinItem(itemId); + const item = document.getElementById(itemId); + if (item) { + let addedItem = sidebarBookmarks.appendChild(item.cloneNode(true)); + + addedItem.lastChild.lastChild.classList.add("pinned"); + + } + }); +} + +function sdExpandAll() { + + const categoryItems = document.querySelectorAll(".content_sidebar .sidebarCategory"); + const side_bar_status = document.querySelector(".content_sidebar").dataset.expanded; + const expand_node = document.getElementsByClassName("expand_node")[0]; + + let display_value = "true"; + + if (side_bar_status === "true") { + + display_value = "none"; + expand_node.innerHTML = ` + + + + + `; + document.querySelector(".content_sidebar").dataset.expanded = "false"; + + } else { + + display_value = "block"; + expand_node.innerHTML = ` + + + + + + `; + document.querySelector(".content_sidebar").dataset.expanded = "true"; + + } + + + categoryItems.forEach(function (categoryItem) { + const displayNamesList = categoryItem.querySelector("ul"); + + if (expand_node) { + + + displayNamesList.style.display = display_value; + } + + }); +} + + + +function createCategoryList() { + const data = LiteGraph.registered_node_types; + const categories = {}; + const pinnedItems = loadPinnedItems(); + for (const objKey in data) { + const category = data[objKey].category; + if (!categories[category]) { + categories[category] = []; + } + categories[category].push(data[objKey]); + } + + + const sortedCategories = Object.keys(categories).sort(); + + + const sortedCategoriesData = {}; + sortedCategories.forEach(category => { + sortedCategoriesData[category] = categories[category]; + }); + + const categoriesList = document.getElementById("content_sidebar"); + + for (const category in sortedCategoriesData) { + const categoryItem = document.createElement("li"); + categoryItem.classList.add("sidebarCategory"); + categoryItem.textContent = category; + + const displayNamesList = document.createElement("ul"); + displayNamesList.style.display = "none"; + categoryItem.appendChild(displayNamesList); + + categories[category].forEach(displayName => { + try { + const displayNameItem = document.createElement("li"); + displayNameItem.classList.add("sidebarItem"); + displayNameItem.textContent = displayName.title; + displayNameItem.title = displayName.title; + displayNameItem.draggable = true; + + displayNameItem.id = displayName.type; + + + const pinButton = document.createElement("button"); + pinButton.classList.add("pinButton"); + + let add_class = ""; + + if (pinnedItems.includes(displayName.type)) { + add_class = "pinned"; + } + pinButton.innerHTML = ` + + + `; + + + displayNameItem.appendChild(pinButton); + + displayNamesList.appendChild(displayNameItem); + } catch (err) { + + } + }); + + categoryItem.addEventListener("click", function (event) { + + if (event.target === event.currentTarget) { + + displayNamesList.style.display = displayNamesList.style.display === "none" ? "block" : "none"; + } + }); + + + categoriesList.appendChild(categoryItem); + } + + + const dragHandle = document.getElementById("dragHandle"); + + let isDragging = false; + let startX; + let startWidth; + + + dragHandle.addEventListener("mousedown", function (event) { + isDragging = true; + startX = event.clientX; + startWidth = parseInt(window.getComputedStyle(categoriesList).width); + + }); + + + document.addEventListener("mouseup", function () { + + if (isDragging) { + isDragging = false; + + saveSidebarWidth(categoriesList); + + } + }); + + + document.addEventListener("mousemove", function (event) { + if (!isDragging) return; + + const delta = event.clientX - startX; + categoriesList.style.width = (startWidth + delta) + "px"; + }); + + loadPinnedItemsAndAddToBookmarks(); +} + +function addSidebar() { + + let draggedElementId; + const sidebar_width = restoreSidebarWidth(); + const sidebarHtml = ` + + + `; + + const sidebarElement = $el("sidebar", { + parent: document.body, + innerHTML: sidebarHtml + }); + + + + const clearIcon = document.querySelector(".clearIcon"); + const searchCategoryIcon = document.querySelector(".searchCategoryIcon"); + + + const searchInput = document.getElementById("searchInput"); + + clearIcon.addEventListener("click", async function () { + try { + + searchInput.value = ""; + + + const searchTerm = await handleSearch(); + + + console.log("Search term cleared:", searchTerm); + + + } catch (error) { + + console.error("Error occurred during search:", error); + } + }); + + searchCategoryIcon.addEventListener("click", async function () { + try { + + categorySearchToggle = !categorySearchToggle; + + + searchCategoryIcon.style.opacity = categorySearchToggle ? "1" : "0.5"; + + + const searchTerm = await handleSearch(); + + + console.log("Search term:", searchTerm); + + + } catch (error) { + + console.error("Error occurred during search:", error); + } + }); + + + + document.getElementById("content_sidebar").addEventListener("click", function (event) { + const clickedElement = event.target; + const pinnedItems = loadPinnedItems(); + + const pinButton = clickedElement.tagName; + + + + if (pinButton == "rect" && clickedElement.classList.contains("pin_node")) { + let itemId; + try { + itemId = clickedElement.parentElement.parentElement.parentElement.id; + } catch (err) { + + } + + if (!pinnedItems.includes(itemId)) { + + const sidebarItemId = itemId; + + + pinItem(sidebarItemId); + clickedElement.parentElement.getElementsByClassName("pin_normal")[0].classList.add("pinned"); + + }//remove from sidebar + else { + if (clickedElement.parentElement.parentElement.parentElement.parentElement.id == "sidebarBookmarks") { + clickedElement.parentElement.parentElement.parentElement.remove(); + + const itemIndex = pinnedItems.indexOf(itemId); + + + removePinnedClass(itemId); + + if (itemIndex !== -1) { + pinnedItems.splice(itemIndex, 1); + savePinnedItems(pinnedItems); + } + } else { + + unpinItem(itemId); + } + + clickedElement.parentElement.getElementsByClassName("pin_normal")[0].classList.remove("pinned"); + } + + + } + else if ((pinButton == "rect") && clickedElement.classList.contains("expand_node")) { + + sdExpandAll(); + } + + }); + + + const sidebarItems = document.querySelectorAll(".sidebar li ul li"); + sidebarItems.forEach(item => { + item.draggable = true; + }); + + setTimeout(() => { + createCategoryList(); + + }, 2000); + + + function convertCanvasToOffset(canvas, pos, out) { + out = out || [0, 0]; + out[0] = pos[0] / canvas.scale - canvas.offset[0]; + out[1] = pos[1] / canvas.scale - canvas.offset[1]; + return out; + }; + + + function handleDrop(event) { + event.preventDefault(); + + const coord = convertCanvasToOffset(app.canvasEl.data.ds, [event.clientX, event.clientY]); + const x = coord[0]; + const y = coord[1]; + + + sidebarAddNode(draggedElementId, draggedElementId, x, y); + const draggedElementId_string = draggedElementId; + + + draggedElementId = null; + } + + + function allowDrop(event) { + event.preventDefault(); + } + + + const canvas = sidebarElement; + + + + canvas.addEventListener("dragstart", function (event) { + + draggedElementId = event.target.id; + }); + + + + function handleSearch(event) { + return new Promise((resolve, reject) => { + const searchTerm = document.getElementById("searchInput").value.toLowerCase(); + const categoryItems = document.querySelectorAll(".sidebarCategory li"); + const categories = document.querySelectorAll(".sidebarCategory"); + const listItems = document.querySelectorAll(".sidebar li"); + + categoryItems.forEach(category => { + const subItems = category.querySelectorAll("li"); + category.style.display = "block"; + + subItems.forEach(sub => { + sub.style.display = "block"; + }); + + }); + + + const sidebarItems = categorySearchToggle ? categories : listItems; + + + sidebarItems.forEach(item => { + + let itemText = item.textContent.toLowerCase(); + if (categorySearchToggle) { + + itemText = Array.from(item.childNodes) + .filter(node => node.nodeType === Node.TEXT_NODE) + .map(node => node.textContent.trim()) + .join(' ').toLowerCase(); + } + + const isInSearchTerm = itemText.includes(searchTerm); + + + + item.style.display = isInSearchTerm ? "block" : "none"; + }); + + + categories.forEach(category => { + const subItems = category.querySelectorAll("li"); + + + + + const areAllHidden = Array.from(subItems).every(subItem => subItem.style.display === "none"); + category.style.display = areAllHidden ? "none" : category.style.display; + }); + + + resolve(searchTerm); + }); + } + + const search_bar = document.getElementById('searchInput'); + search_bar.addEventListener("input", async (event) => { + try { + + const searchTerm = await handleSearch(event); + + + console.log("Search term:", searchTerm); + + + } catch (error) { + + console.error("Error occurred during search:", error); + } + }); + + + document.getElementById('switch_sidebar').addEventListener('click', function () { + const side_bar = document.getElementById('content_sidebar'); + + search_bar.classList.toggle('closed'); + side_bar.classList.toggle('closed'); + clearIcon.classList.toggle('closed'); + searchCategoryIcon.classList.toggle('closed'); + + }); + + document.addEventListener('drop', handleDrop); + + + canvas.addEventListener('dragover', allowDrop); + + +} + + + + + + + +addSidebarStyles(); +addSidebar(); + + +/* +function handleKeyPress(event) { + if (event.key === "x") { } +} +document.addEventListener("keydown", handleKeyPress); +*/