Rewritten a lot of the code to improve performance and readability, and slightly adjusted the UI to ensure compatibility with future updates.

ADDEDD CUSTOM CATEGORIES
Added custom setting panel
Re-Implemented the 'original' search function to support translated versions of ComfyUI (requires testing)
Added LEFT and RIGHT positioning options
Added "Auto Hide" feature
Now you have the option to use the default node ordering in ComfyUI instead of the alphabetical one
Added the possibility to perform a Soft or Factory Reset of the configuration
Now most configuration settings are stored in the settings.json file.
Fixed some bugs
Added some other bugs for sure!
This commit is contained in:
Nuked
2024-04-21 21:53:09 +02:00
parent 4ded5ec0f7
commit d01019587b
23 changed files with 4644 additions and 2020 deletions
+3
View File
@@ -1,3 +1,6 @@
# Config file
app/settings.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+22 -2
View File
@@ -11,6 +11,18 @@ For what i know nobody did it, so i did it.
Maybe you don't need it. I need it >.<
# Updates
- 21-04-2024
- Rewritten a lot of the code to improve performance and readability, and slightly adjusted the UI to ensure compatibility with future updates.
- ADDEDD CUSTOM CATEGORIES
- Added custom setting panel
- Re-Implemented the 'original' search function to support translated versions of ComfyUI (requires testing)
- Added LEFT and RIGHT positioning options
- Added "Auto Hide" feature
- Now you have the option to use the default node ordering in ComfyUI instead of the alphabetical one
- Added the possibility to perform a Soft or Factory Reset of the configuration
- Now most configuration settings are stored in the settings.json file.
- Fixed some bugs
- Added some other bugs for sure!
- 31-03-2024
- Added fuzzy search (work in reverse too!) (enabled by default) - You can disable it in the settings
@@ -62,6 +74,11 @@ to your ComfyUI `custom_nodes` directory
![Fuzzy Search](./images/fuzzysearch.gif)
This feature is enabled by default, you can disable it in the settings. I've used fts_fuzzy_match.js by [Forrest Smith](https://github.com/forrestthewoods/lib_fts)
### 🎨 Custom Categories 📂
![Custom Categories](./images/custom_categories.gif)
### ➕Expand/Collapse Categories/Sidebar➖
![Expand/Collapse Categories and Sidebar](./images/expand_collapse.gif)
@@ -76,7 +93,7 @@ This feature is enabled by default, you can disable it in the settings. I've use
### 🎨 ComfyUI Themes Support 🎨
![Themes Support ](./images/theme.gif)
### 🎨 Custom Values From ComfyUI Settings 🎨
### 🎨 New Settings Panel 🎨
![ComfyUI Settings](./images/settings.png)
### Additional Features
@@ -87,7 +104,9 @@ This feature is enabled by default, you can disable it in the settings. I've use
- [x] Reordering pinned nodes
- [x] Node preview (i don't think it will be an image)
- [x] Color integration with Jovimetrix
- [ ] Better search
- [x] Better search
- [x] Custom Categories!!
## Contributing
@@ -99,6 +118,7 @@ Feel free to contribute to this project by reporting issues or suggesting improv
If you'd like to support the project, consider making a donation ❤️
<br>
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C0AJECJ)
<a href="https://patreon.com/DreamingAIChannel"><img src="./images/patreon_badge.png" alt="Support me on Patreon" height="27" /></a>
## License
+121 -1
View File
@@ -1,6 +1,126 @@
import server
import folder_paths
from aiohttp import web
import os
import json
class JSONConfigManager:
def __init__(self, file_name):
self.file_name = file_name
self.initial_config = """
{"menuctx_category": "Context Menu",
"menuctx_options": [],
"menuctx_subOptions": [],
"menuctx_opt_callback": [],
"menuctx_sub_opt_callback": []
}"""
if os.path.isfile(file_name):
self.data = self.read_json_file()
else:
self.data = json.loads(self.initial_config)
self.write_json_file()
def read_json_file(self):
try:
with open(self.file_name, 'r') as file:
return json.load(file)
except FileNotFoundError:
return {}
def write_json_file(self):
with open(self.file_name, 'w') as file:
json.dump(self.data, file, indent=4)
def read_item(self, parameter_name):
return self.data.get(parameter_name)
def update_item(self, parameter_name, new_value):
self.data[parameter_name] = new_value
self.write_json_file()
def delete_item(self, parameter_name):
if parameter_name in self.data:
del self.data[parameter_name]
self.write_json_file()
def add_item(self, parameter_name, value):
self.data[parameter_name] = value
self.write_json_file()
file_path = os.path.join(folder_paths.get_folder_paths("custom_nodes")[0],"ComfyUI-N-Sidebar","app","settings.json")
config_manager = JSONConfigManager(file_path)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
print(folder_paths.get_folder_paths("custom_nodes"))
list_panels = os.path.join(folder_paths.get_folder_paths("custom_nodes")[0],"ComfyUI-N-Sidebar","app","panels")
list_panels_array = []
#list folders
folders = os.listdir(list_panels)
for folder in folders:
if os.path.isdir(os.path.join(list_panels, folder)):
list_panels_array.append(folder)
WEB_DIRECTORY = "./js"
@server.PromptServer.instance.routes.get("/sidebar/panels" )
async def s_get(request):
result = {"panels": list_panels_array}
return web.json_response(result, content_type='application/json')
@server.PromptServer.instance.routes.post("/sidebar/settings" )
async def s_get(request):
data = await request.json()
action = data["action"]
if action == "add":
parameter = data["parameter"]
value = data["value"]
config_manager.add_item(parameter, value)
result = {"result": "OK"}
return web.json_response(result, content_type='application/json')
elif action == "update":
parameter = data["parameter"]
value = data["value"]
config_manager.update_item(parameter, value)
result = {"result": "OK"}
return web.json_response(result, content_type='application/json')
elif action == "delete":
parameter = data["parameter"]
config_manager.delete_item(parameter)
result = {"result": "OK"}
return web.json_response(result, content_type='application/json')
elif action == "read":
parameter = data["parameter"]
read_language = config_manager.read_item(parameter)
result = {"value": read_language}
return web.json_response(result, content_type='application/json')
elif action == "factory_reset":
config_manager.data = json.loads(config_manager.initial_config)
config_manager.write_json_file()
result = {"result": "OK"}
return web.json_response(result, content_type='application/json')
else:
result = {"result": "ERROR"}
return web.json_response(result, content_type='application/json')
WEB_DIRECTORY = "./app"
+756
View File
@@ -0,0 +1,756 @@
.litegraph .dialog {
z-index: 100 !important;
}
.sidebar {
position: absolute;
top: 0;
left: -250px;
left: -250px;
width: fit-content;
height: calc(100% - 19px);
color: var(--drag-text);
transition: left 0.3s ease;
z-index: 2;
padding-top: 19px;
left: 0;
user-select: none;
}
.sidebar ul {
list-style-type: none;
/* border-bottom: 6px solid rgb(from var(--comfy-input-bg) r g b / 60%);*/
padding-left: 5px;
padding-right: 5px;
margin-bottom: 8px;
margin-top: 0;
padding-top: 5px;
}
.sidebar li {
padding: 10px;
cursor: pointer;
user-select: none;
color: var(--input-text);
}
.content_sidebar {
background: rgb(from var(--comfy-menu-bg) r g b / 100%);
overflow-y: scroll;
overflow-x: hidden;
height: 100%;
float: left;
backdrop-filter: blur(5px);
width: calc(100% - 45px);
}
.dragHandle {
position: relative;
float: left;
right: 0;
top: 0;
height: 100%;
width: 5px;
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: var(--comfy-input-bg);
color: var(--input-text);
line-height: 1.4;
border: 1px solid var(--border-color);
}
.sidebar-header {
position: absolute;
width: calc(100% - 75px);
margin-top: 5px;
margin-bottom: 10px;
margin-left: 10px;
z-index: 400;
}
.clearIcon, .searchCategoryIcon, .clearIconCustomCategory, .searchCategoryIconCustomCategory {
position: absolute;
padding: 5px;
right: 30px;
color: var(--input-text);
font-size: x-large;
cursor: pointer;
opacity: 0.5;
user-select: none;
line-height: 1.4;
top: 0;
}
.clearIcon:hover, .clearIconCustomCategory:hover {
opacity: 1.0;
}
.searchCategoryIcon, .searchCategoryIconCustomCategory {
right: 0px;
padding: 3px;
font-size: larger;
background: var(--border-color);
color: var(--drag-text);
margin: 4px;
border-radius: 5px;
width: 18px;
text-align: center;
top: 0;
}
.searchCategoryIcon:hover, .searchCategoryIconCustomCategory:hover {
opacity: 0.5;
}
.sidebarCategory, #sidebarBookmarks {
list-style-type: none;
font-family: 'Open Sans', sans-serif;
text-transform: capitalize;
margin: 2px;
background-color: var(--comfy-input-bg);
opacity: 1;
border-radius: 8px;
padding-top: 11px;
font-size: 15px;
padding-bottom: 8px;
}
#sidebarCustomNodes {
list-style-type: none;
font-family: 'Open Sans', sans-serif;
text-transform: capitalize;
margin: 0px;
background-color: transparent;
opacity: 1;
border-radius: 8px;
font-size: 15px;
padding-left: 0;
padding-right: 0px;
padding-top: 0px;
padding-bottom: 40px;
}
.sidebarItem:hover {
background: color-mix(in srgb, currentColor 15%, transparent);
}
.sidebarItem {
list-style-type: none;
font-family: 'Open Sans', sans-serif;
text-transform: capitalize;
margin: 2px;
background: color-mix(in srgb, var(--border-color) 35%, transparent);
opacity: 1;
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;
}
.view_button {
position: relative;
float: unset;
cursor: pointer;
user-select: none;
padding: 5px;
font-size: 20px;
background-color: var(--comfy-input-bg);
opacity: 0.7;
border-radius: 5px;
margin-top:2px;
}
.view_button:hover {
opacity: 1.0;
}
#sidebar_views {
position: relative;
float: left;
text-align: center;
cursor: pointer;
user-select: none;
padding: 0px 2px 2px 2px;
font-size: 20px;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
height: 100%;
width: 30px;
border: 0;
border-left: 2px rgb(from var(--comfy-menu-bg) r g b / 100%);
border-style: solid;
padding-bottom: 0px;
backdrop-filter: blur(16px) !important;
background: rgb(from var(--comfy-menu-bg) r g b / 100%);
}
#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;
}
.previewButton {
background-color: transparent;
border: 0;
position: absolute;
right: 35px;
}
.pinned {
fill: var(--descrip-text) !important;
opacity: 1 !important;
}
.svg_class {
width: 24px;
height: 24px;
fill: var(--border-color);
cursor: pointer;
/* hacky fix */
margin-top: -5px;
margin-left: -25px;
}
.pin_normal {
opacity: 0.5;
}
.pin_normal:hover {
opacity: 1;
}
.svg_class:hover {
fill: var(--border-color);
}
#sidebarBookmarks .sidebarItem {
margin-left: 10px;
margin-right: 29px;
}
.sb_label {
font-family: 'Open Sans', sans-serif;
position: relative;
margin: 5px;
font-weight: bold;
background: var(--comfy-input-bg);
border-radius: 3px;
padding-left: 6px;
padding-right: 6px;
display: block;
width: calc(100% - 20px);
text-align: center;
user-select: none;
cursor: pointer;
}
.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: var(--input-text);
cursor: pointer;
}
.sb_dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: grey;
}
.node_header {
line-height: 1;
padding: 8px 13px 7px;
background: var(--comfy-input-bg);
margin-bottom: 5px;
font-size: 15px;
text-wrap: nowrap;
overflow: hidden;
}
.headdot {
width: 10px;
height: 10px;
float: inline-start;
margin-right: 8px;
}
.sb_hidden {
display: none;
}
.IMAGE {
background-color: #64b5f6;
}
.VAE {
background-color: #ff6e6e;
}
.LATENT {
background-color: #ff9cf9;
}
.MASK {
background-color: #81c784;
}
.CONDITIONING {
background-color: #ffa931;
}
.CLIP {
background-color: #ffd500;
}
.MODEL {
background-color: #b39ddb;
}
.CONTROL_NET {
background-color: #a5d6a7;
}
#previewDiv {
position: absolute;
background-color: var(--comfy-menu-bg);
font-family: 'Open Sans', sans-serif;
font-size: small;
color: var(--descrip-text);
border: 1px solid var(--descrip-text);
display: none;
min-width: 300px;
width: min-content;
height: fit-content;
z-index: 9999;
border-radius: 12px;
overflow: hidden;
font-size: 12px;
padding-bottom: 10px;
}
#previewDiv .sb_description {
margin: 10px;
padding: 6px;
background: var(--border-color);
border-radius: 5px;
font-style: italic;
font-weight: 500;
font-size: 0.9rem;
}
.sb_table {
display: grid;
grid-column-gap: 10px;
/* Spazio tra le colonne */
width: 100%;
/* Imposta la larghezza della tabella al 100% del contenitore */
}
.sb_row {
display: grid;
grid-template-columns: 10px 1fr 1fr 1fr 10px;
grid-column-gap: 10px;
align-items: baseline;
padding-left: 9px;
padding-right: 9px;
}
.sb_row_string {
grid-template-columns: 10px 1fr 1fr 10fr 1fr;
}
.sb_col {
border: 0px solid #000;
display: flex;
align-items: flex-end;
flex-direction: row-reverse;
flex-wrap: nowrap;
align-content: flex-start;
justify-content: flex-end;
}
.sb_inherit {
display: inherit;
}
.long_field {
background: var(--bg-color);
border: 2px solid var(--border-color);
margin: 5px 5px 0 5px;
border-radius: 10px;
line-height: 1.7;
}
.sb_arrow {
color: var(--fg-color);
}
.sb_preview_badge {
text-align: center;
background: var(--comfy-input-bg);
font-weight: bold;
color: var(--error-text);
}
#sb_scrollToTopButton {
position: absolute;
bottom: 5px;
right: 56px;
cursor: pointer;
display: none;
border-radius: 20%;
background: var(--comfy-menu-bg);
padding: 4px;
border: 1px solid #4c4c4c;
}
#sb_scrollToTopButton svg {
width: 20px;
height: 20px;
fill: var(--descrip-text);
}
.panel_sidebar{
min-height: 200px;
border-radius: 5px;
margin-left: 7px;
}
.panel_sidebar label{
text-transform: uppercase;
}
.custom-menu {
box-shadow: inset 0px 0px 0px rgb(44 44 44 / 5%), inset 1px -1px 0px 0px rgb(22 22 22 / 50%), 0px 0px 9px 0px rgb(0, 0, 0);
font-family: Arial, sans-serif;
display: none;
position: absolute;
background-color: var(--bg-color);
border: 0px solid #313131;
color: var(--input-text);
padding: 5px 0;
border-radius: 5px;
z-index: 9999;
/* backdrop-filter: blur(5px);
background: rgb(from #353535 r g b / 50%);*/
}
.custom-menu ul {
list-style-type: none;
padding: 0;
margin: 0;
width: 100%;
}
.custom-menu li {
padding: 8px 15px;
cursor: pointer;
}
#menu-options li {
min-width: 134px;
}
.custom-menu .sub-menu li:first-child {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.custom-menu .sub-menu li:last-child {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.custom-menu li:hover {
background-color: var(--border-color);
}
.sub-menu {
box-shadow: inset 0px 0px 0px rgb(44 44 44 / 5%), inset 1px -1px 0px 0px rgb(22 22 22 / 50%), 0px 0px 9px 0px rgb(0, 0, 0);
font-family: Arial, sans-serif;
display: none;
position: absolute;
top: 0;
left: 100%;
background-color: var(--bg-color);
border: 0px solid #313131;
border-radius: 5px;
border-top-left-radius: 0px;
z-index: 1001; /* Ensure sub-menu appears above main menu */
/*
backdrop-filter: blur(5px);
background: rgb(from #353535 r g b / 50%);*/
}
.cat_empty {
display: none !important;
}
/* Settings */
#switch_settings{
bottom: 5px;
position: absolute;
width: 19px;
height: 20px;
line-height: 1;
}
#sb_settingsDiv{
outline: 0;
border: 0;
border-radius: 6px;
background: var(--bg-color);
color: var(--input-text);
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0);
max-width: 800px;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-size: 0.8rem;
padding: 5px 20px 20px 20px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1001;
text-transform: uppercase;
font-weight: bold;
}
#sb_settingsDiv .setting{
height: 28px;
width: 450px;
margin-bottom: 7px;
}
#sb_settingsDiv .setting input[type="range"]{
height: 28px;
}
#sb_settingsDiv .setting input[type="checkbox"]{
width: 18px;
height: 18px;
}
#sb_settingsDiv .setting input{
float: right;
}
#sb_settingsDiv .setting label{
line-height: 1.8;
}
#sb_settingsDiv .slider-container{
float: right;
width: 36px;
background: var(--comfy-menu-bg);
padding: 3px;
border-radius: 5px;
text-align: center;
margin: 5px auto;
}
#sb_settingsDiv h1{
margin: 0 0 20px 0;
padding: 0 0 5px 0;
text-align: center;
border-bottom: 5px solid var(--border-color);
text-transform: uppercase;
}
#sb_settingsDiv button{
padding: 9px;
/* position: absolute; */
bottom: 0;
width: 100%;
margin: 0 0 20px 0;
background: var(--comfy-menu-bg);
color: var(--input-text);
border: 1px solid var(--border-color);
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
}
#sb_settingsDiv button:hover{
filter: brightness(120%);
}
#sb_settingsDiv i{
margin-left: 4px;
color: #0075ff;
font-style: normal;
font-size: larger;
}
/* MODALS */
.sb-modal-title{
position: absolute;
top: 4px;
margin-top: 0;
text-transform: uppercase;
}
.sb-modal-backdrop, .sb-modal-backdrop-settings {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2); /* Add desired opacity */
z-index: 1000; /* Make sure the backdrop is behind the modal */
}
#sb_settingsDiv select{
background: var(--comfy-menu-bg);
color: var(--drag-text);
padding: 5px;
float: right;
width: 129px;
margin-right: 47px;
border-radius: 5px;
}
.colorBlob {
float: right;
overflow: hidden;
margin-top: 34px;
width: 100%;
height: 125px;
border-radius: 6px;
}
.colorBlob input[type='color'] {
width: 106%;
height: 112%;
margin: -6px;
}
#switch_home,#switch_custom_view{
width: 1.3rem;
height: 1.3rem;
}
#switch_sidebar{
margin-top: 5px;
}
+49
View File
@@ -0,0 +1,49 @@
<div class="sidebar" id="sidebar" style="width:{{sidebar_width}};">
<div class="sidebar-header" id="sidebar-header" data-content="panel_head_home">
<input type="text" id="searchInput" placeholder="Search..." autocomplete="off">
<span class="clearIcon">&times;</span>
<span class="searchCategoryIcon">C</span>
</div>
<div class="content_sidebar" id ="content_sidebar_home" data-expanded="false" data-content="panel_content_home" >
<div id="spacer"></div>
<label class="sb_label" id="sb_label_pinned">PINNED</label>
<ul id="sidebarBookmarks"></ul>
<label class="sb_label" id="sb_label_custom">CUSTOM NODES<button class="expand_node" >
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M48,9.5C48,8.7,47.3,8,46.5,8h-41C4.7,8,4,8.7,4,9.5v3C4,13.3,4.7,14,5.5,14h41c0.8,0,1.5-0.7,1.5-1.5V9.5z"
/>
<path d="M48,39.5c0-0.8-0.7-1.5-1.5-1.5h-41C4.7,38,4,38.7,4,39.5v3C4,43.3,4.7,44,5.5,44h41c0.8,0,1.5-0.7,1.5-1.5
V39.5z"/>
<path d="M30,29h4.5c0.8,0,1.5-0.7,1.5-1.5v-3c0-0.8-0.7-1.5-1.5-1.5H30c-0.6,0-1-0.4-1-1v-4.5c0-0.8-0.7-1.5-1.5-1.5
h-3c-0.8,0-1.5,0.7-1.5,1.5V22c0,0.6-0.4,1-1,1h-4.5c-0.8,0-1.5,0.7-1.5,1.5v3c0,0.8,0.7,1.5,1.5,1.5H22c0.6,0,1,0.4,1,1v4.5
c0,0.8,0.7,1.5,1.5,1.5h3c0.8,0,1.5-0.7,1.5-1.5V30C29,29.4,29.4,29,30,29z"/>
<rect class="expand_node" style="opacity:0" x="0" y="0" width="52" height="52" />
</svg>
</button></label>
<ul id="sidebarCustomNodes"></ul>
</div>
<div id="sidebar_views">
<div class="view_button" id ="switch_sidebar">☰</div>
<div class="view_button" id ="switch_home" data-content="home"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100; fill:var(--input-text);" xml:space="preserve"><g><path d="M29,82h13V63.1c0-1.7,1.3-3,3-3h10.3c1.7,0,3,1.3,3,3V82H71c1.7,0,3-1.3,3-3V43.2c0-0.8-0.3-1.6-0.9-2.1l-21-21 c-1.2-1.2-3.1-1.2-4.2,0l-21,21c-0.6,0.6-0.9,1.3-0.9,2.1V79C26,80.7,27.3,82,29,82z"/></g></svg></div>
<div class="view_button" id ="switch_settings">⚙</div>
</div>
<div class="dragHandle" id="dragHandle"></div>
<div id="previewDiv"></div>
<div id="sb-modal-backdrop-settings" class="sb-modal-backdrop-settings sb_hidden"></div>
<div id="sb_settingsDiv" class="sb_hidden"><h1>SideBar Settings</h1>
<button id="sb_resetButton">Soft Reset</button> <button id="sb_factoryResetButton">Factory Reset</button></div>
<a href="#" id="sb_scrollToTopButton">
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 330 330" xml:space="preserve">
<path id="XMLID_224_" d="M325.606,229.393l-150.004-150C172.79,76.58,168.974,75,164.996,75c-3.979,0-7.794,1.581-10.607,4.394
l-149.996,150c-5.858,5.858-5.858,15.355,0,21.213c5.857,5.857,15.355,5.858,21.213,0l139.39-139.393l139.397,139.393
C307.322,253.536,311.161,255,315,255c3.839,0,7.678-1.464,10.607-4.394C331.464,244.748,331.464,235.251,325.606,229.393z"/>
</svg>
</a>
<div id="customMenu" class="custom-menu">
<ul> </ul></div>
</div>
+152
View File
@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Context Menu</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background-color: rgb(26, 26, 26);
}
.context-menu {
display: none;
position: absolute;
background-color: #3b3b3b;
border: 1px solid #313131;
color: #d4d4d4;
padding: 5px 0;
border-radius: 5px;
z-index: 1000;
}
.context-menu ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.context-menu li {
padding: 8px 15px;
cursor: pointer;
}
.context-menu li:hover {
background-color: #6d6d6d;
}
.sub-menu {
display: none;
position: absolute;
top: 0;
left: 100%;
background-color: #3b3b3b;
border: 1px solid #313131;
border-radius: 5px;
z-index: 1001; /* Ensure sub-menu appears above main menu */
}
</style>
</head>
<body oncontextmenu="return false;">
<div class="context-menu" id="context-menu">
</div>
<script>
const options = ["op1", "op2", "op3"];
const subOptions = [
["sub1", "sub2"],
["sub3", "sub4"],
["sub5", "sub6", "sub44", "sub26"]
];
let subMenuVisible = false;
contextMenu = document.getElementById('context-menu');
const menuOptions = document.createElement("ul");
menuOptions.id = "menu-options";
contextMenu.appendChild(menuOptions);
const subMenus = [];
for (let i = 0; i < subOptions.length; i++) {
const subMenu = document.createElement("ul");
subMenu.id = `sub-menu-op${i + 1}`;
subMenu.classList.add("sub-menu");
contextMenu.appendChild(subMenu);
subMenus.push(subMenu);
for (let j = 0; j < subOptions[i].length; j++) {
const li = document.createElement("li");
li.textContent = subOptions[i][j];
subMenu.appendChild(li);
li.addEventListener('click', () => {
alert(`Selected option: ${subOptions[i][j]}`);
})
}
}
function createContextMenu(event) {
event.preventDefault();
hideAllSubMenus();
const contextMenu = document.getElementById('context-menu');
const menuOptions = document.getElementById('menu-options');
// Clear existing menu items
menuOptions.innerHTML = '';
// Populate main options
options.forEach((option, index) => {
const li = document.createElement('li');
li.textContent = option;
li.addEventListener('mouseenter', () => {
hideAllSubMenus();
showSubMenu(subMenus[index], li, index);
});
li.addEventListener('click', () => {
// Handle main option click
alert(`Selected option: ${option}`);
});
menuOptions.appendChild(li);
});
// Show context menu
contextMenu.style.display = 'block';
contextMenu.style.left = event.clientX + 'px';
contextMenu.style.top = event.clientY + 'px';
// Hide context menu on body click
document.body.addEventListener('click', hideContextMenu);
}
function showSubMenu(subMenu, element, index) {
subMenu.style.display = 'block';
const rect = element.getBoundingClientRect();
subMenu.style.top = element.offsetTop + 'px';
subMenuVisible = true;
}
function hideAllSubMenus() {
const subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(subMenu => {
subMenu.style.display = 'none';
});
subMenuVisible = false;
}
function hideContextMenu() {
const contextMenu = document.getElementById('context-menu');
contextMenu.style.display = 'none';
document.body.removeEventListener('click', hideContextMenu);
}
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('contextmenu', createContextMenu);
});
document.addEventListener('click', hideContextMenu);
</script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100; fill:var(--input-text);" xml:space="preserve"><g><path d="M29,82h13V63.1c0-1.7,1.3-3,3-3h10.3c1.7,0,3,1.3,3,3V82H71c1.7,0,3-1.3,3-3V43.2c0-0.8-0.3-1.6-0.9-2.1l-21-21 c-1.2-1.2-3.1-1.2-4.2,0l-21,21c-0.6,0.6-0.9,1.3-0.9,2.1V79C26,80.7,27.3,82,29,82z"/></g></svg>

After

Width:  |  Height:  |  Size: 439 B

+404
View File
@@ -0,0 +1,404 @@
const cnPath="../extensions/ComfyUI-N-Sidebar/"
function addSidebarStyles(cssPath) {
const linkElement = document.createElement("link");
linkElement.rel = "stylesheet";
linkElement.type = "text/css";
linkElement.href = cnPath+cssPath
document.head.appendChild(linkElement);
}
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 sdExpandAll(forceExpand=0) {
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" && forceExpand === 0) {
display_value = "none";
expand_node.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M48,9.5C48,8.7,47.3,8,46.5,8h-41C4.7,8,4,8.7,4,9.5v3C4,13.3,4.7,14,5.5,14h41c0.8,0,1.5-0.7,1.5-1.5V9.5z"
/>
<path d="M48,39.5c0-0.8-0.7-1.5-1.5-1.5h-41C4.7,38,4,38.7,4,39.5v3C4,43.3,4.7,44,5.5,44h41c0.8,0,1.5-0.7,1.5-1.5
V39.5z"/>
<path d="M30,29h4.5c0.8,0,1.5-0.7,1.5-1.5v-3c0-0.8-0.7-1.5-1.5-1.5H30c-0.6,0-1-0.4-1-1v-4.5c0-0.8-0.7-1.5-1.5-1.5
h-3c-0.8,0-1.5,0.7-1.5,1.5V22c0,0.6-0.4,1-1,1h-4.5c-0.8,0-1.5,0.7-1.5,1.5v3c0,0.8,0.7,1.5,1.5,1.5H22c0.6,0,1,0.4,1,1v4.5
c0,0.8,0.7,1.5,1.5,1.5h3c0.8,0,1.5-0.7,1.5-1.5V30C29,29.4,29.4,29,30,29z"/>
<rect class="expand_node" style="opacity:0" x="0" y="0" width="52" height="52" />
</svg>`;
document.querySelector(".content_sidebar").dataset.expanded = "false";
} else {
display_value = "block";
expand_node.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve" >
<path d="M48,9.5C48,8.7,47.3,8,46.5,8h-41C4.7,8,4,8.7,4,9.5v3C4,13.3,4.7,14,5.5,14h41c0.8,0,1.5-0.7,1.5-1.5V9.5z"></path>
<path d="M48,39.5c0-0.8-0.7-1.5-1.5-1.5h-41C4.7,38,4,38.7,4,39.5v3C4,43.3,4.7,44,5.5,44h41c0.8,0,1.5-0.7,1.5-1.5
V39.5z"></path>
<path d="M34.5,29c0.8,0,1.5-0.7,1.5-1.5v-3c0-0.8-0.7-1.5-1.5-1.5h-17c-0.8,0-1.5,0.7-1.5,1.5v3
c0,0.8,0.7,1.5,1.5,1.5H34.5z"></path>
<rect class="expand_node" style="opacity:0" x="0" y="0" width="52" height="52" />
</svg>`;
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 handleSearch(categorySearchToggle,searchSection,searchIdInput) {
return new Promise((resolve, reject) => {
const options = {
keys: ['textContent','category'],
threshold: 0.5,
useExtendedSearch: true
};
const searchTerm = document.getElementById(searchIdInput).value.toLowerCase();
const categoryItems = document.querySelectorAll(".sidebarCategory li");
const categories = document.querySelectorAll(searchSection+" .sidebarCategory");
const listItems = document.querySelectorAll(searchSection+" .sidebarItem");
/*reset*/
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();
}
//fts_fuzzy_match(searchTerm, cleanText(itemText))
const isInSearchTerm = itemText.includes(searchTerm);
item.style.display = isInSearchTerm ? "block" : "none";
});
/* hide empty categories */
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;
});
sdExpandAll(1);
resolve(searchTerm);
});
}
//PREVIEW
function safeObjectKeys(obj) {
try {
return Object.keys(obj);
} catch (error) {
//console.error('Error while trying to get object keys:', error);
return [];
}
}
function createNodePreview(nodeID) {
let description = "";
let rows = "";
let last_rows = "";
const data = LiteGraph.registered_node_types;
try{
description = data[nodeID].nodeData.description;
}catch(err){
description = "";
}
try{
let inputs = data[nodeID].nodeData.input; //divided between optional and required
let outputs_name = data[nodeID].nodeData.output_name;
let outputs = data[nodeID].nodeData.output;
//console.log(category, inputs, outputs);
//create array with objectkeys from input and output
const inputArray = [];
const outputArray = [];
const inputKeysRequired = safeObjectKeys(inputs.required)
const inputKeysOptional = safeObjectKeys(inputs.optional)
for (let i = 0; i < inputKeysRequired.length; i++) {
try{
let thirdV = null;
let secondV = inputs.required[inputKeysRequired[i]][0];
if (Array.isArray(secondV)){
//array
secondV = inputs.required[inputKeysRequired[i]][0][0];
thirdV = inputs.required[inputKeysRequired[i]][0][0];
}
if (inputs.required[inputKeysRequired[i]][1]!=undefined){
//object
thirdV = inputs.required[inputKeysRequired[i]][1].default;
}
inputArray.push([inputKeysRequired[i],secondV,thirdV]);
}catch(err){
console.log("error",err)
}
}
try{
for (let i = 0; i < inputKeysOptional.length; i++) {
let thirdV = null;
let secondV = inputs.optional[inputKeysOptional[i]][0];
if (Array.isArray(secondV)){
//array
secondV = inputs.optional[inputKeysOptional[i]][0][0];
thirdV = inputs.optional[inputKeysOptional[i]][0][0];
}
if (inputs.optional[inputKeysOptional[i]][1]!=undefined){
//object
thirdV = inputs.optional[inputKeysOptional[i]][1].default;
}
inputArray.push([inputKeysOptional[i],secondV,thirdV]);
}
}catch(err){
console.log(err)
}
for (let i = 0; i < outputs.length; i++) {
if (outputs_name[i] != undefined) {
outputArray.push([outputs[i],outputs_name[i]]);
}
else{
outputArray.push(outputs[i],outputs[i]);
}
}
let length_loop = outputArray.length;
if (inputArray.length> outputArray.length){
length_loop = inputArray.length;
}
for (let i = 0; i < length_loop; i++) {
const inputList= inputArray[i] ? inputArray[i] : null;
const outputList= outputArray[i] ? outputArray[i] : null;
let inputName = "";
let inputType = "";
let inputValue = null;
let outputType = "";
let outputName = "";
if (inputList != null){
inputName = inputList[0];
inputType = inputList[1];
inputValue = inputList[2];
}else
{
inputName = "";
inputType = "sb_hidden";
inputValue = null;
}
if (outputList != null){
outputType = outputList[0];
outputName = outputList[1];
}else
{
outputType = "sb_hidden";
outputName = "";
}
if (inputValue != null){
if (inputType == "STRING"){
last_rows += `<div class="sb_row sb_row_string nodepreview long_field">
<div class="sb_col sb_arrow"></div>
<div class="sb_col">${inputName}</div>
<div class="sb_col middle-column"></div>
<div class="sb_col sb_inherit">${inputValue}</div>
<div class="sb_col sb_arrow"></div>
</div>`;
}else{
last_rows += `<div class="sb_row nodepreview long_field">
<div class="sb_col sb_arrow">&#x25C0;</div>
<div class="sb_col ">${inputName}</div>
<div class="sb_col middle-column"></div>
<div class="sb_col sb_inherit">${inputValue}</div>
<div class="sb_col sb_arrow">&#x25B6;</div>
</div>`;
}
inputType = "sb_hidden";
inputName = "";
}
rows += `<div class="sb_row nodepreview">
<div class="sb_col"><div class="sb_dot ${inputType}"></div></div>
<div class="sb_col">${inputName}</div>
<div class="sb_col middle-column"> </div>
<div class="sb_col sb_inherit">${outputName}</div>
<div class="sb_col "><div class="sb_dot ${outputType}"></div></div>
</div>`;
}
//console.log( inputArray,outputArray);
////create preview
//const nodePreview = document.createElement("div");
//nodePreview.classList.add("node_preview");
//nodePreview.id = nodeID;
}catch(err){
rows = `<div class="sb_row nodepreview">
<div class="sb_col"></div>
<div class="sb_col"></div>
<div class="sb_col middle-column sb_preview_badge ">NOT AVAILABLE</div>
<div class="sb_col sb_inherit"></div>
<div class="sb_col "></div>
</div>`
last_rows = ``
}
return [`<div class="sb_table">
<div class="node_header"><div class="sb_dot headdot"></div>${nodeID}</div>
<div class="sb_preview_badge">PREVIEW</div>
${rows}
${last_rows}
</div>`,description];
}
//change view
function switchTab(tabId) {
if (tabId != null) {
const tabContents = document.querySelectorAll('.content_sidebar');
const tabHead = document.querySelectorAll('.sidebar-header');
if (tabId=="content_sidebar_home"){
const tabIdHead = document.getElementById("sidebar-header");
tabIdHead.classList.remove('sb_hidden');
}
else{
tabHead.forEach(content => {
content.classList.add('sb_hidden');
})
}
// Hide all tabs
tabContents.forEach(content => {
content.classList.add('sb_hidden');
});
// Show the content of the clicked tab
document.getElementById(tabId).classList.remove('sb_hidden');
setVar('sb_current_tab', tabId);
}
}
// PINNED ITEMS
function savePinnedItems(pinnedItems) {
const pinnedItemsString = JSON.stringify(pinnedItems);
//setVar('pinnedItems', pinnedItemsString, 9999);
updateConfiguration('sb_pinnedItems',pinnedItemsString)
}
async function loadPinnedItems() {
const pinnedItemsString = await getConfiguration('sb_pinnedItems');//getVar('pinnedItems');
if (pinnedItemsString) {
return JSON.parse(pinnedItemsString);
}
return [];
}
function migrationSettings() {
const oldSettingsList = ["pinnedItems","Comfy.Settings.sidebar_noderadius_settings","Comfy.Settings.sidebar_barbottom","Comfy.Settings.sidebar_bartop","Comfy.Settings.sidebar_blur_settings","Comfy.Settings.sidebar_opacity_settings","Comfy.Settings.sidebar_font_settings","Comfy.Settings.sidebar_node_settings"];
const newSettingsList = ["sb_pinnedItems","sb_noderadius","sb_barbottom","sb_bartop","sb_blur","sb_opacity","sb_font","sb_node"];
oldSettingsList.forEach((setting, index) => {
try{
const newSetting = newSettingsList[index];
const oldSettingValue = getVar(setting);
if (oldSettingValue) {
if (setting != "pinnedItems") {
updateConfiguration(newSetting, parseFloat(oldSettingValue.replace('"',"").replace('"',"")));
} else {
updateConfiguration(newSetting, oldSettingValue);
}
renameVar(setting, "bk_"+setting)
}
}catch(err){
console.log(err)
}
})
// updateConfiguration('sb_pinnedItems',pinnedItemsString) ;
}
+481
View File
@@ -0,0 +1,481 @@
let sbPosition = "left";
let defaultSearchToggle = "fuzzy";
let defaultSearchOrder = "comfyui";
async function getConfiguration(name) {
//console.log("getStyles called " + name);
try {
const response = await fetch('/sidebar/settings', {
cache: "no-store",
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action : "read",
parameter: name
}),
});
const data = await response.json();
return data["value"];
} catch (error) {
console.log(error);
return null;
}
}
function addConfiguration(name,value) {
return fetch('/sidebar/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action : "add",
parameter: name,
value: value
}),
})
}
function updateConfiguration(name,value) {
return fetch('/sidebar/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action : "update",
parameter: name,
value: value
}),
})
}
function removeConfiguration(name) {
return fetch('/sidebar/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action : "delete",
parameter: name
}),
})
}
function factoryResetSettings() {
return fetch('/sidebar/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action : "factory_reset"
})
})
}
function settingsSetup(app,$el) {
app.ui.settings.addSetting({
id: "0_sidebar",
name: "[Sidebar] Better ComfyUI Settings Style",
type: "boolean",
defaultValue: false,
onChange(value) {
let styleElement;
if (value) {
styleElement = $el("style", {
id: "sb-settings-style",
parent: document.head,
textContent: settingsStyle
});
} else {
try{
document.getElementById("sb-settings-style").remove();
} catch(e) {
}
}
},
});
addSBSetting( "sb_settingsDiv",{
id: "font",
name: "Font Size",
defaultValue: "13",
type: "slider",
attrs: {
min: 5,
max: 30,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.sidebarCategory, #sidebarBookmarks', 'font-size', value + 'px');
},
});
addSBSetting( "sb_settingsDiv",{
id: "node",
name: "Node Size",
defaultValue: "10",
type: "slider",
attrs: {
min: 2,
max: 40,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.sidebar li', 'padding', value + 'px');
},
});
addSBSetting( "sb_settingsDiv", {
id: "bartop",
name: "Space Top",
defaultValue: "19",
type: "slider",
attrs: {
min: 0,
max: 300,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.sidebar', 'padding-top', value + 'px');
}
});
addSBSetting( "sb_settingsDiv", {
id: "barbottom",
name: "Space Bottom",
defaultValue: "19",
type: "slider",
attrs: {
min: 0,
max: 300,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.sidebar', 'height', 'calc(100% - ' + value + 'px');
}
});
addSBSetting( "sb_settingsDiv",{
id: "noderadius",
name: "Node Radius Border",
defaultValue: "9",
type: "slider",
attrs: {
min: 0,
max: 20,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.sidebarCategory, #sidebarBookmarks', 'border-radius', value + 'px');
addDynamicCSSRule('.sidebarItem', 'border-radius', value + 'px');
},
});
addSBSetting( "sb_settingsDiv",{
id: "blur",
name: "Blur Intesity",
defaultValue: "5",
type: "slider",
attrs: {
min: 0,
max: 30,
step: 1,
},
onChange(value) {
addDynamicCSSRule('.content_sidebar', 'backdrop-filter', 'blur(' + value + 'px)');
addDynamicCSSRule('#sidebar_views', 'backdrop-filter', 'blur(' + value + 'px)');
},
});
addSBSetting( "sb_settingsDiv", {
id: "opacity",
name: "Opacity",
defaultValue: "1.0",
type: "slider",
info: "Note: The opacity of custom-colored nodes is not applied in real-time and will take effect after the page is refreshed.",
attrs: {
min: 0.1,
max: 1,
step: 0.1,
},
onChange(value) {
let value_perc = (value * 100).toFixed(0);
addDynamicCSSRule('.content_sidebar', 'background', 'rgb(from var(--comfy-menu-bg) r g b / ' + value_perc + '%)');
addDynamicCSSRule('#sidebar_views', 'background', 'rgb(from var(--comfy-menu-bg) r g b / ' + value_perc + '%)');
addDynamicCSSRule('#sidebar_views', 'border-left', '2px rgb(from var(--comfy-menu-bg) r g b / ' + (parseFloat(value) + 10) + '%)');
addDynamicCSSRule('.sidebarCategory, #sidebarBookmarks', 'background-color', 'rgb(from var(--comfy-input-bg) r g b / ' + value_perc + '%)');
},
});
addSBSetting( "sb_settingsDiv", {
id: "search_type",
name: "Search Type",
defaultValue: "fuzzy",
type: "dropdown",
info: "Select the search type:\nORIGINAL: People who use custom nodes to translate ComfyUI (e.g. Chinese, Japanese...) should use this!\nNORMAL: Search based on original node name but without fuzzy matching\nFUZZY: Search based on the original node name with fuzzy matching (Default).",
options: [
{ value: "original", label: "ORIGINAL" },
{ value: "normal", label: "NORMAL" },
{ value: "fuzzy", label: "FUZZY" },
],
onChange(value) {
defaultSearchToggle = value;
},
});
addSBSetting( "sb_settingsDiv", {
id: "position",
name: "Position",
defaultValue: "left",
type: "dropdown",
options: [
{ value: "left", label: "LEFT" },
{ value: "right", label: "RIGHT" }
],
onChange(value) {
if (value == "right") {
addDynamicCSSRule('.content_sidebar', 'float', 'right');
addDynamicCSSRule('#sidebar_views', 'float', 'right');
addDynamicCSSRule('.dragHandle', 'float', 'right');
addDynamicCSSRule('.sidebar', 'left', 'unset');
addDynamicCSSRule('.sidebar', 'right', '0');
addDynamicCSSRule('.sidebar-header', 'right', '16px');
addDynamicCSSRule('#sb_scrollToTopButton', 'right', '12px');
}
if (value == "left") {
addDynamicCSSRule('.content_sidebar', 'float', 'left');
addDynamicCSSRule('#sidebar_views', 'float', 'left');
addDynamicCSSRule('.dragHandle', 'float', 'left');
addDynamicCSSRule('.sidebar', 'left', '0');
addDynamicCSSRule('.sidebar', 'right', 'unset');
addDynamicCSSRule('.sidebar-header', 'right', 'unset');
addDynamicCSSRule('#sb_scrollToTopButton', 'right', '56px');
}
sbPosition = value;
},
});
addSBSetting( "sb_settingsDiv", {
id: "order_type",
name: "Nodes Order Type",
defaultValue: "comfyui",
type: "dropdown",
info: "Select the order type:\nCOMFYUI: The order of nodes will be the same as in the original ComfyUI\nALPHABETICAL: The order of nodes will be alphabetical\nNOTE: The new order will be applied after a page refresh!",
options: [
{ value: "comfyui", label: "COMFYUI" },
{ value: "alphabetical", label: "ALPHABETICAL" }
],
onChange(value) {
defaultSearchOrder = value;
},
});
const switch_settings = document.getElementById("switch_settings");
const sb_modal_backdrop = document.getElementById("sb-modal-backdrop-settings");
switch_settings.addEventListener('click', function() {
//set sb-settingsDiv visible
const sb_settingsDiv = document.getElementById("sb_settingsDiv");
sb_settingsDiv.classList.remove('sb_hidden');
sb_modal_backdrop.classList.remove('sb_hidden');
})
document.body.addEventListener('click', function(event) {
const modalBackdrop = document.querySelector('.sb-modal-backdrop-settings');
if (event.target === modalBackdrop) {
sb_settingsDiv.classList.add('sb_hidden');
sb_modal_backdrop.classList.add('sb_hidden');
}
})
sb_resetButton.addEventListener('click', function() {
resetSettings();
})
sb_factoryResetButton.addEventListener('click', function() {
factoryReset();
})
}
async function addSBSetting(fieldLocation, setting) {
setting.id = "sb_" + setting.id; // Aggiunge il prefisso del campo
// Crea l'elemento HTML per il campo di impostazione
const settingElement = document.createElement("div");
settingElement.classList.add("setting");
// Aggiungi il nome del campo
const nameLabel = document.createElement("label");
nameLabel.textContent = setting.name;
settingElement.appendChild(nameLabel);
if (setting.info) {
const infoIcon = document.createElement("i");
infoIcon.classList.add("info-icon");
infoIcon.innerHTML ="🛈";
infoIcon.title = setting.info;
nameLabel.appendChild(infoIcon);
}
// Aggiungi il tipo appropriato di input per il campo
let inputElement;
if (setting.type === "boolean") {
inputElement = document.createElement("input");
inputElement.type = "checkbox";
inputElement.checked = await getConfiguration(setting.id) || setting.defaultValue;
setting.onChange(inputElement.checked);
inputElement.addEventListener("change", function() {
setting.onChange(inputElement.checked);
updateConfiguration(setting.id,inputElement.checked)
});
} else if (setting.type === "slider") {
// Crea l'elemento input per lo slider
const sliderContainer = document.createElement("div");
sliderContainer.classList.add("slider-container");
inputElement = document.createElement("input");
inputElement.type = "range";
inputElement.min = setting.attrs.min;
inputElement.max = setting.attrs.max;
inputElement.step = setting.attrs.step;
inputElement.value = await getConfiguration(setting.id) || setting.defaultValue || setting.defaultValue;
setting.onChange(inputElement.value);
inputElement.addEventListener("input", function() {
// Aggiorna il valore del campo testuale quando lo slider viene spostato
valueText.textContent = inputElement.value;
setting.onChange(inputElement.value);
updateConfiguration(setting.id,inputElement.value)
});
// Crea l'elemento per il valore testuale dello slider
const valueText = document.createElement("span");
valueText.textContent = await getConfiguration(setting.id) || setting.defaultValue;
sliderContainer.appendChild(inputElement);
sliderContainer.appendChild(valueText);
settingElement.appendChild(sliderContainer);
} else if (setting.type === "text") {
inputElement = document.createElement("input");
inputElement.type = "text";
inputElement.value = await getConfiguration(setting.id) || setting.defaultValue;
setting.onChange(inputElement.value);
inputElement.addEventListener("input", function() {
setting.onChange(inputElement.value);
updateConfiguration(setting.id,inputElement.value)
});
} else if (setting.type === "dropdown") {
inputElement = document.createElement("select");
const defaultValue = await getConfiguration(setting.id) || setting.defaultValue;
setting.options.forEach(async (option) => {
const optionElement = document.createElement("option");
optionElement.value = option.value;
optionElement.textContent = option.label;
if (option.value === defaultValue) {
optionElement.selected = true;
}
inputElement.appendChild(optionElement);
});
setting.onChange(defaultValue);
inputElement.addEventListener("change", function() {
setting.onChange(inputElement.value);
updateConfiguration(setting.id,inputElement.value)
});
}
settingElement.appendChild(inputElement);
// Aggiungi il campo di impostazione al luogo specificato
const targetElement = document.getElementById(fieldLocation);
if (targetElement) {
targetElement.appendChild(settingElement);
} else {
console.error("Target element not found!");
}
}
function resetSettings() {
let ok = confirm("This option will reset the following settings: \n-Search Type\n-Position\n-Nodes Order Type\n-Font Size\n-Node Size\n-Space Top\n-Space Bottom\n-Node Radius Border\n-Blur Intesity\n-Opacity\nAre you sure you want to reset these settings?");
if (!ok) {
return;
}
const actualSettings = ["sb_noderadius","sb_barbottom","sb_bartop","sb_blur","sb_opacity","sb_font","sb_node"];
actualSettings.forEach(setting => {
removeConfiguration(setting);
});
window.location.reload();
}
function factoryReset() {
let ok = confirm("Are you sure you want to do a factory reset?");
if (!ok) {
return;
}
factoryResetSettings();
const localSettings = ["sidebarWidth","sb_pinned_collapsed","sb_minimized","sb_current_tab","sb_current_tab"];
localSettings.forEach(setting => {
try{
removeVar(setting);
}catch(err){
console.log(err)
}
})
window.location.reload();
}
+496
View File
@@ -0,0 +1,496 @@
let new_menu_options = [];
let new_submenu_options = [];
let new_menu_options_callback = [];
let new_submenu_options_callback = [];
const settingsStyle = `
#comfy-settings-dialog input{
background: var(--comfy-input-bg);
color: var(--input-text);
border: 1px solid var(--border-color);
padding: 5px;
}
#comfy-settings-dialog tr > td:first-child {
text-align: left;
}
#comfy-settings-dialog select{
background: var(--bg-color);
color: var(--drag-text);
padding: 5px;
}
#comfy-settings-dialog {
background: var(--bg-color);
}
#comfy-settings-dialog::-webkit-scrollbar {
margin-top: 0.5rem;
height: 1rem;
width: .6rem;
top: 10px;
background-color: transparent;
}
#comfy-settings-dialog::-webkit-scrollbar:horizontal {
height: .5rem;
width: 2.5rem
}
#comfy-settings-dialog::-webkit-scrollbar-track {
background-color: var(--comfy-input-bg);
border-radius: 9999px
}
#comfy-settings-dialog::-webkit-scrollbar-thumb {
--tw-border-opacity: 1;
background-color: var(--border-color);
border: 0;
border-radius: 9999px;
}
#comfy-settings-dialog::-webkit-scrollbar-thumb:hover {
--tw-bg-opacity: 1;
background-color: var(--border-color);
}
.comfy-table td, .comfy-table th {
border: 0px solid var(--border-color);
padding: 8px;
}
`
async function api_get(url) {
var response = await api.fetchApi(url, { cache: "no-store" })
return await response.json()
}
function addDynamicCSSRule(selector, property, value) {
const styleId = 'dynamic-styles';
let customStyle = document.getElementById(styleId);
if (!customStyle) {
customStyle = document.createElement('style');
customStyle.type = 'text/css';
customStyle.id = styleId;
document.head.appendChild(customStyle);
}
const existingRule = Array.from(customStyle.sheet.cssRules).find(rule => rule.selectorText === selector);
if (existingRule) {
existingRule.style[property] = value;
} else {
customStyle.sheet.insertRule(`${selector} { ${property}: ${value} !important; }`, customStyle.sheet.cssRules.length);
}
}
function addJavascriptVar(name, value) {
const scripta = document.createElement('script');
scripta.type = 'text/javascript';
console.log("var "+name+" = "+value)
scripta.innerHTML = "var "+name+" = "+value;
document.head.appendChild(scripta);
}
function getVar(name) {
var varValue = localStorage.getItem(name);
if (varValue === null) {
if (name=="pinnedItems") {
//get cookie pinnedItems
var cookies = document.cookie.split('; ');
cookies.forEach(cookie => {
const [cookieName, cookieValue] = cookie.split('=');
if (cookieName === name) {
setVar(name, cookieValue)
}
})
//delete cookie pinnedItems
document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
else {
return null;
}
}else {
return varValue;
}
return null;
}
function setVar(name, value) {
/*migration to localstorage*/
localStorage.setItem(name, value);
}
function removeVar(name) {
localStorage.removeItem(name);
}
function renameVar(oldName, newName) {
const oldValue = getVar(oldName);
setVar(newName, oldValue);
removeVar(oldName);
}
function getElementPosition(element) {
const rect = element.getBoundingClientRect();
return {
top: rect.top,
left: rect.left
};
}
function reverseAndAppend(originalName) {
let words;
let lastWord;
let reversedWords;
let reversedString;
let finalName;
if (originalName.includes(' ')) {
const words = originalName.split(' ');
lastWord = words.pop();
reversedWords = words.reverse();
reversedString = reversedWords.join(' ');
finalName = originalName + ' ' + reversedString;
return finalName;
} else {
words = originalName.split(/(?=[A-Z])/);
lastWord = words.pop();
reversedWords = words.reverse();
reversedString = reversedWords.join('');
finalName = originalName + reversedString;
return finalName;
}
}
function cleanText(text) {
let cleanedText = text.replace(/[^a-zA-Z0-9\s\n\-]/g, '');
cleanedText = cleanedText.replace(/\s+/g, ' ');
cleanedText = cleanedText.replace(/\n/g, '');
return cleanedText;
}
//CONTEXT MENU
function createContextMenu(event, subMenus,settingsData) {
event.preventDefault();
hideAllSubMenus();
const sbcontextMenu = document.getElementById('customMenu');
const menuOptions = document.getElementById('menu-options');
// Clear existing menu items
menuOptions.innerHTML = '';
// Populate main options
settingsData.menuctx_options.forEach((option, index) => {
const li = document.createElement('li');
li.textContent = option;
li.addEventListener('mouseenter', () => {
hideAllSubMenus();
showSubMenu(subMenus[index], li);
});
try {
if (typeof (window[settingsData.menuctx_opt_callback[index]]) === 'function') {
li.addEventListener('click', () => {
// Handle main option click
//alert(`Selected option: ${option}`);
window[settingsData.menuctx_opt_callback[index]](event);
});
}
} catch (error) {
console.log(error);
}
menuOptions.appendChild(li);
});
// Show context menu
sbcontextMenu.style.display = 'block';
sbcontextMenu.style.left = event.clientX + 'px';
sbcontextMenu.style.top = event.clientY + 'px';
// Hide context menu on body click
document.body.addEventListener('click', hideContextMenu);
}
function showSubMenu(subMenu, element) {
try{
if (subMenu) {
subMenu.style.display = 'block';
const rect = element.getBoundingClientRect();
subMenu.style.top = element.offsetTop + 'px';
subMenuVisible = true;
}
} catch (error) {
console.log(error);
}
}
function hideAllSubMenus() {
const subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(subMenu => {
subMenu.style.display = 'none';
});
subMenuVisible = false;
}
function hideContextMenu() {
const sbcontextMenu = document.getElementById('customMenu');
sbcontextMenu.style.display = 'none';
document.body.removeEventListener('click', hideContextMenu);
}
let subMenuVisible = false;
let lastTargetClicked = null;
async function setContextMenu(settingsData,class_menu_item,main=0,exluded_class=null,included_class = null) {
if (main==1){
settingsData.menuctx_options = settingsData.menuctx_options.concat(new_menu_options);
settingsData.menuctx_subOptions = settingsData.menuctx_subOptions.concat(new_submenu_options);
settingsData.menuctx_opt_callback = settingsData.menuctx_opt_callback.concat(new_menu_options_callback);
settingsData.menuctx_sub_opt_callback = settingsData.menuctx_sub_opt_callback.concat(new_submenu_options_callback);
}
const sbcontextMenu = document.getElementById('customMenu');
const menuOptions = document.createElement("ul");
menuOptions.id = "menu-options";
sbcontextMenu.appendChild(menuOptions);
const subMenus = [];
for (let i = 0; i < settingsData.menuctx_subOptions.length; i++) {
const subMenu = document.createElement("ul");
subMenu.id = `sub-menu-op${i + 1}`;
subMenu.classList.add("sub-menu");
if (settingsData.menuctx_subOptions[i].length == 0) {
subMenu.classList.add("cat_empty");
}
sbcontextMenu.appendChild(subMenu);
subMenus.push(subMenu);
for (let j = 0; j < settingsData.menuctx_subOptions[i].length; j++) {
const li = document.createElement("li");
li.textContent = settingsData.menuctx_subOptions[i][j];
subMenu.appendChild(li);
try {
if (typeof (window[settingsData.menuctx_sub_opt_callback[i][j]]) === 'function') {
li.addEventListener('click', () => {
// Handle main option click
//alert(`Selected option: ${settingsData.menuctx_subOptions[i][j]}`);
window[settingsData.menuctx_sub_opt_callback[i][j]](lastTargetClicked,settingsData.menuctx_subOptions[i][j]);
});
}
} catch (error) {
console.log(error);
}
}
}
const sidebarItems = document.querySelectorAll(class_menu_item);
//const sb =document.getElementById('content_sidebar_home');
for (const sidebarItem of sidebarItems) {
sidebarItem.addEventListener('contextmenu', (event) => {
if(!event.target.classList.contains(exluded_class)){
if (included_class != null && !event.target.classList.contains(included_class)){
return;
}
createContextMenu (event, subMenus,settingsData);
lastTargetClicked = event;
}
});
}
//ContextMenu
//sb.addEventListener("contextmenu", function(event) {
// event.preventDefault();
//
// const customMenu = document.getElementById("customMenu");
// customMenu.style.display = "block";
// customMenu.style.left = event.pageX + "px";
// customMenu.style.top = event.pageY + "px";
//});
//
window.addEventListener("click", function(event) {
const customMenu = document.getElementById("customMenu");
customMenu.style.display = "none";
});
}
// Function to check for special characters
function containsSpecialCharacters(name) {
// Define a regular expression pattern to match special characters
var specialCharacters = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/;
// Return true if the name contains any special characters, false otherwise
return specialCharacters.test(name);
}
async function reloadCtxMenu() {
const cnPath="../extensions/ComfyUI-N-Sidebar/"
const response3 = await fetch(cnPath +'settings.json');
const settingsData = await response3.json();
setContextMenu(settingsData,"#content_sidebar_home .sidebarItem",1);
}
function setNodeStatus(node,status) {
try{
const categoryNodeStatus = JSON.parse(localStorage.getItem('sb_categoryNodeStatus')) || {};
categoryNodeStatus[node] = [status];
localStorage.setItem('sb_categoryNodeStatus', JSON.stringify(categoryNodeStatus));
}catch(err){ }
}
function getNodeStatus(node) {
try{
const categoryNodeStatus = JSON.parse(localStorage.getItem('sb_categoryNodeStatus')) || {};
return categoryNodeStatus[node] || "none";
}catch(err){ }
return "none";
}
function getNodesStatus() {
let categoryNodeStatus = JSON.parse(localStorage.getItem('sb_categoryNodeStatus')) || {};
return categoryNodeStatus;
}
// MODAL
function setModalContext(addon=null) {
if (addon==null) {
return `
<h2 class="sb-modal-title">Add New Category</h2>
<form id="sb-category-form" action="javascript:void(0);">
<input type="text" autocomplete="off" class="sb-input" id="sb-categoryName" name="sb-categoryName" placeholder="Category Name" required>
<button type="submit" class="sb-button" onclick="addCategory()">Add Category</button>
</form>
<button id="closeModalButton" class="sb-button">X</button>
`;
}
else {
return addon
}
}
function openModal(addon=null) {
// Create HTML elements for the modal
const modalBackdrop = document.createElement('div');
modalBackdrop.classList.add('sb-modal-backdrop');
const modal = document.createElement('div');
modal.classList.add('sb-category-dialog');
modal.innerHTML = setModalContext(addon)
// Add the modal to the DOM
document.body.appendChild(modalBackdrop);
document.body.appendChild(modal);
// Add an event listener for the close button
const closeModalButton = modal.querySelector('#closeModalButton');
closeModalButton.addEventListener('click', closeModal);
try{
// Focus the input field
document.getElementById('sb-categoryName').focus();
}catch(err){
}
}
// Function to close the modal
function closeModal() {
const modalBackdrop = document.querySelector('.sb-modal-backdrop');
const modal = document.querySelector('.sb-category-dialog');
// Remove the modal from the DOM
modalBackdrop.parentNode.removeChild(modalBackdrop);
modal.parentNode.removeChild(modal);
}
function rgbToHex(rgb) {
// Verifica se il colore è nel formato "rgb()"
if (rgb.indexOf('rgb') !== -1) {
// Esegue il parsing dei valori di rosso, verde e blu
const [r, g, b] = rgb.match(/\d+/g);
// Converte i valori in esadecimale e li concatena con "#"
return `#${parseInt(r, 10).toString(16).padStart(2, '0')}${parseInt(g, 10).toString(16).padStart(2, '0')}${parseInt(b, 10).toString(16).padStart(2, '0')}`;
}
return rgb; // Se il colore è già nel formato con "#", lo restituisce direttamente
}
function hexToRgb(hex) {
try{
// Rimuovi il carattere # se presente
hex = hex.replace(/^#/, '');
// Estrai i valori R, G e B
let r = parseInt(hex.substring(0, 2), 16);
let g = parseInt(hex.substring(2, 4), 16);
let b = parseInt(hex.substring(4, 6), 16);
// Restituisci il colore in formato RGB
return `${r}, ${g}, ${b}`;
}catch(err){
console.log(err)
return "#000000";
}
}
//click anywhere outside the modal to close it
document.body.addEventListener('click', function(event) {
const modalBackdrop = document.querySelector('.sb-modal-backdrop');
const modal = document.querySelector('.sb-category-dialog');
if (event.target === modalBackdrop) {
closeModal();
}
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
<style>
</style>
<script>
</script>
<div id ="custom_categories_header">
<div class="h-header" id="h_search" data-content="panel_head_home">
<input type="text" id="searchInputCn" oninput="searchT(this)" placeholder="Search..." autocomplete="off" class="">
<span class="clearIconCustomCategory">×</span>
<span class="searchCategoryIconCustomCategory">C</span> <button id="openModalButton" onclick="openModal()">+</button>
</div>
</div>
<div id="custom_categories_main" >
</div>
@@ -0,0 +1,829 @@
//console.log(LiteGraph.registered_node_types);
let currentElement = null;
function createCustomHtml(type, name, elem = null) {
if (type == "rename") {
return `<h2 class="sb-modal-title">Rename Category</h2>
<form id="sb-category-form" action="javascript:void(0);">
<input type="text" autocomplete="off" class="sb-input" id="sb-categoryName" value="${name}" name="sb-categoryName" required>
<button type="submit" class="sb-button" onclick="renameCategory('${name}',document.getElementById('sb-categoryName').value)">Rename Category</button>
</form>
<button id="closeModalButton" class="sb-button">X</button>
`
} else if (type == "color") {
currentElement = elem;
return `<h2 class="sb-modal-title">Color ${name}</h2>
<div class="colorBlob">
<input class="colorBlobPicker" type="color" oninput="colorCategory('${name}',this.value)" value="${rgbToHex(elem.style.backgroundColor)}" id="colorPicker">
</div>
<button type="button" class="sb-button" onclick="resetColorCategory('${name}')">Reset Color</button>
<button type="button" class="sb-button" onclick="removeKeyConfig('sb_ColorCustomCategories') ">Reset All Colors</button>
<button id="closeModalButton" class="sb-button">X</button>
`
}
}
// Function to open the modal
//insert button in label with id panel_title_custom_categories
let categorySearchToggleCustom = false;
const panel_title_custom_categories = document.getElementById("panel_title_custom_categories");
const button_custom_categories = document.createElement("button");
button_custom_categories.classList = "expand_node";
button_custom_categories.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M48,9.5C48,8.7,47.3,8,46.5,8h-41C4.7,8,4,8.7,4,9.5v3C4,13.3,4.7,14,5.5,14h41c0.8,0,1.5-0.7,1.5-1.5V9.5z"></path>
<path d="M48,39.5c0-0.8-0.7-1.5-1.5-1.5h-41C4.7,38,4,38.7,4,39.5v3C4,43.3,4.7,44,5.5,44h41c0.8,0,1.5-0.7,1.5-1.5
V39.5z"></path>
<path d="M34.5,29c0.8,0,1.5-0.7,1.5-1.5v-3c0-0.8-0.7-1.5-1.5-1.5h-17c-0.8,0-1.5,0.7-1.5,1.5v3
c0,0.8,0.7,1.5,1.5,1.5H34.5z"></path>
<rect class="expand_node" style="opacity:0" x="0" y="0" width="52" height="52"></rect>
</svg>`;
panel_title_custom_categories.appendChild(button_custom_categories);
button_custom_categories.addEventListener("click", (event) => {
const clickedElement = event.target;
const pinButton = clickedElement.tagName;
if ((pinButton == "rect") && clickedElement.classList.contains("expand_node")) {
sdExpandAll();
}
})
const clearIconCustomCategory = document.querySelector(".clearIconCustomCategory");
const itemSearchInput = document.getElementById("searchInputCn");
clearIconCustomCategory.addEventListener("click", async function () {
try {
itemSearchInput.value = "";
renderList("custom_categories_main");
} catch (error) {
console.error("Error occurred during search:", error);
}
});
function afterRender() {
customMenu = `
{
"menuctx_category": "Context Menu",
"menuctx_options": [
"Rename Category",
"Delete Category",
"Change Color"
],
"menuctx_subOptions": [
[
]
],
"menuctx_opt_callback": [
"renameCategoryCallback",
"deleteCategory",
"colorCategoryCallback"
],
"menuctx_sub_opt_callback": [
[
],
[
]
]
}`
customMenuItem = `
{
"menuctx_category": "Context Menu",
"menuctx_options": [
"Delete from Category"
],
"menuctx_subOptions": [
[
]
],
"menuctx_opt_callback": [
"removeNodeFromCategoryCallback"
],
"menuctx_sub_opt_callback": [
[
],
[
]
]
}`
customMenuJSON = JSON.parse(customMenu);
customMenuItemJSON = JSON.parse(customMenuItem);
setContextMenu(customMenuJSON, "#custom_categories_main .sidebarCategory", 0, "sidebarItem", "sidebarCategory");
setContextMenu(customMenuItemJSON, "#custom_categories_main .sidebarItem");
// ORDER CUSTOM CATEGORIES
const custom_categories_main = document.getElementById("custom_categories_main");
custom_categories_main.querySelectorAll(".sidebarCategory").forEach(function (item) {
item.addEventListener("dragstart", function (event) {
dragItem = event.target;
});
});
custom_categories_main.addEventListener("dragover", function (event) {
event.preventDefault();
});
custom_categories_main.addEventListener("drop", function (event) {
event.preventDefault();
if (dragItem) {
if (dragItem.parentElement === event.target.parentElement) {
custom_categories_main.insertBefore(dragItem, event.target);
reorderCategories(getCustomCategoryOrder());
}
dragItem = null;
}
});
// ORDER CUSTOM NODES
custom_categories_main.querySelectorAll(".sidebarItem").forEach(function (item) {
item.addEventListener("dragstart", function (event) {
dragItem = event.target;
});
});
}
function getCustomCategoryOrder() {
let cat = [];
const custom_categories_main = document.getElementById("custom_categories_main");
custom_categories_main.querySelectorAll(".sidebarCategory").forEach(function (item) {
cat.push(item.dataset.namecategory)
});
return cat
}
function getCustomNodeOrder(idcat) {
let node = [];
const objMain = document.getElementById(idcat);
objMain.querySelectorAll(".sidebarItem").forEach(function (item) {
node.push(item.dataset.type)
});
return node
}
async function renderList(elementID) {
const data = LiteGraph.registered_node_types;
const myPromise = new Promise(async (resolve, reject) => {
const categories = await readCategories();
//const pinnedItems = loadPinnedItems();
//console.log(categories);
const sidebarCustomNodes = document.getElementById(elementID);
//clear elementid
sidebarCustomNodes.innerHTML = "";
for (const category in categories) {
//data[objKey].title.toLowerCase();
let categoryName = categories[category]
const categoryItem = document.createElement("li");
categoryItem.classList.add("sidebarCategory");
categoryItem.dataset.namecategory = categoryName;
categoryItem.textContent = categoryName;
categoryItem.draggable = true;
const currentColor = await getValueFromConfig("sb_ColorCustomCategories", categoryName);
if (currentColor) {
const value_perc = await getConfiguration("sb_opacity") || "1.0";
const rgbColor = hexToRgb(currentColor);
categoryItem.style.backgroundColor = `rgba(${rgbColor}, ${value_perc})`;
}
const displayNamesList = document.createElement("ul");
displayNamesList.id = categoryName + "_ul";
displayNamesList.style.display = getNodeStatus(categoryName);
categoryItem.appendChild(displayNamesList);
let displayName = category;
const listNodeForCategory = await getNodesForCategory(categoryName);
listNodeForCategory.forEach(displayName => {
try {
const displayNameItem = document.createElement("li");
displayNameItem.classList.add("sidebarItem");
displayNameItem.textContent = data[displayName].title;
displayNameItem.title = data[displayName].title;
displayNameItem.draggable = true;
displayNameItem.dataset.type = data[displayName].type;
displayNameItem.id = displayName.replace(" ", "_");
/* Create Pin Button
const pinButton = document.createElement("button");
pinButton.classList.add("pinButton");
let add_class = "";
if (pinnedItems.includes(displayNameItem.dataset.type)) {
add_class = "pinned";
}
pinButton.innerHTML = `<svg class="svg_class" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path class="pin_normal ${add_class}" d="M19.1835 7.80516L16.2188 4.83755C14.1921 2.8089 13.1788 1.79457 12.0904 2.03468C11.0021 2.2748 10.5086 3.62155 9.5217 6.31506L8.85373 8.1381C8.59063 8.85617 8.45908 9.2152 8.22239 9.49292C8.11619 9.61754 7.99536 9.72887 7.86251 9.82451C7.56644 10.0377 7.19811 10.1392 6.46145 10.3423C4.80107 10.8 3.97088 11.0289 3.65804 11.5721C3.5228 11.8069 3.45242 12.0735 3.45413 12.3446C3.45809 12.9715 4.06698 13.581 5.28476 14.8L6.69935 16.2163L2.22345 20.6964C1.92552 20.9946 1.92552 21.4782 2.22345 21.7764C2.52138 22.0746 3.00443 22.0746 3.30236 21.7764L7.77841 17.2961L9.24441 18.7635C10.4699 19.9902 11.0827 20.6036 11.7134 20.6045C11.9792 20.6049 12.2404 20.5358 12.4713 20.4041C13.0192 20.0914 13.2493 19.2551 13.7095 17.5825C13.9119 16.8472 14.013 16.4795 14.2254 16.1835C14.3184 16.054 14.4262 15.9358 14.5468 15.8314C14.8221 15.593 15.1788 15.459 15.8922 15.191L17.7362 14.4981C20.4 13.4973 21.7319 12.9969 21.9667 11.9115C22.2014 10.826 21.1954 9.81905 19.1835 7.80516Z" />
<rect class="pin_node" style="opacity:0" x="0" y="0" width="24" height="24" />
</svg>`;
displayNameItem.appendChild(pinButton);
/* End Pin Button */
displayNamesList.appendChild(displayNameItem);
displayNamesList.addEventListener("drop", function (event) {
event.preventDefault();
if (dragItem) {
if (dragItem.parentElement == event.target.parentElement) {
displayNamesList.insertBefore(dragItem, event.target);
reorderNodeInCategory(categoryItem.dataset.namecategory, getCustomNodeOrder(dragItem.parentElement.id))
}
dragItem = null;
}
});
}
catch (err) {
console.log(err);
}
})
try {
sidebarCustomNodes.appendChild(categoryItem);
} catch (err) {
console.log(err);
}
}
// Preview
const sidebarItems_cat = document.querySelectorAll('#' + elementID + ' .sidebarItem');
const previewDiv = document.getElementById('previewDiv');
sidebarItems_cat.forEach(item => {
item.addEventListener('mouseover', function () {
if (this.classList.contains('sidebarItem') && this.tagName === 'LI') {
let descriptionDiv = "";
const itemPosition = getElementPosition(this);
let previewDivTop = 0;
const [previewContent, node_description] = createNodePreview(item.dataset.type);
if (node_description) {
descriptionDiv = "<div class='sb_description'>" + node_description + "</div>";
}
previewDiv.innerHTML = previewContent + descriptionDiv;
previewDiv.style.display = 'block';
const correction_offset = 45;
if (itemPosition.top - this.offsetHeight >= 0 && itemPosition.top + previewDiv.offsetHeight < document.body.offsetHeight) {
previewDivTop = itemPosition.top - this.offsetHeight
} else if (itemPosition.top - this.offsetHeight - previewDiv.offsetHeight <= 0) {
previewDivTop = 0 + correction_offset;
}
else {
previewDivTop = (itemPosition.top + this.offsetHeight) - previewDiv.offsetHeight;
}
const previewDivLeft = itemPosition.left + this.offsetWidth + correction_offset;
previewDiv.style.top = `${previewDivTop}px`;
previewDiv.style.left = `${previewDivLeft}px`;
}
});
item.addEventListener('mouseout', function () {
previewDiv.style.display = 'none';
})
});
const categoriesList = document.getElementsByClassName("content_sidebar")[1];
categoriesList.addEventListener('scroll', function () {
previewDiv.style.display = 'none';
});
window.addEventListener('click', function (event) {
if (!event.target.classList.contains('sidebarItem')) {
previewDiv.style.display = 'none';
}
});
const categoryItems = document.querySelectorAll("#custom_categories_main .sidebarCategory");
categoryItems.forEach(function (categoryItem) {
categoryItem.addEventListener("click", function (event) {
if (event.target === event.currentTarget) {
const displayNamesList = event.target.querySelector("ul");
displayNamesList.style.display = displayNamesList.style.display === "none" ? "block" : "none";
setNodeStatus(displayNamesList.parentElement.dataset.namecategory, displayNamesList.style.display)
}
});
});
resolve(afterRender);
});
myPromise.then((callback) => {
callback();
}).catch((error) => {
console.error("An error occurred:", error);
});
}
function searchT(e) {
if (e.value != "") {
//search in all .sidebarItem
handleSearch(categorySearchToggleCustom, "#custom_categories_main", "searchInputCn")
} else {
renderList("custom_categories_main");
}
}
async function getNodeMap() {
let categoryNodeMap = JSON.parse(await getConfiguration('sb_categoryNodeMap')) || {};
return categoryNodeMap;
}
function getNodesStatus() {
let categoryNodeStatus = JSON.parse(localStorage.getItem('sb_categoryNodeStatus')) || {};
return categoryNodeStatus;
}
async function addCategory() {
const newCategoryName = document.getElementById('sb-categoryName').value;
// Check if the category name contains special characters
if (containsSpecialCharacters(newCategoryName)) {
// Alert the user and return if special characters are found
alert("Category name cannot contain special characters.");
return;
}
let categoryNodeMap = await getNodeMap();
if (!categoryNodeMap[newCategoryName]) {
categoryNodeMap[newCategoryName] = [];
updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap));
createContextualMenu();
reloadCtxMenu()
renderList("custom_categories_main");
closeModal()
} else {
alert("Category already exists!");
return;
}
}
// Function to rename a category
async function renameCategory(oldCategoryName, newCategoryName) {
if (containsSpecialCharacters(newCategoryName)) {
// Alert the user and return if special characters are found
alert("Category name cannot contain special characters.");
return;
}
if (oldCategoryName === newCategoryName) {
closeModal()
return;
}
let categoryNodeMap = await getNodeMap();
let categoryNodeStatus = getNodesStatus();
if (categoryNodeMap[oldCategoryName]) {
// Categoty Current Order
let keys = Object.keys(categoryNodeMap);
categoryNodeMap[newCategoryName] = categoryNodeMap[oldCategoryName];
delete categoryNodeMap[oldCategoryName];
// Rebuild Order
let updatedMap = {};
keys.forEach(key => {
if (key === oldCategoryName) {
updatedMap[newCategoryName] = categoryNodeMap[newCategoryName];
} else {
updatedMap[key] = categoryNodeMap[key];
}
});
updateConfiguration('sb_categoryNodeMap', JSON.stringify(updatedMap));
//rename status
if (categoryNodeStatus[oldCategoryName]) {
categoryNodeStatus[newCategoryName] = categoryNodeStatus[oldCategoryName];
delete categoryNodeStatus[oldCategoryName];
localStorage.setItem('sb_categoryNodeStatus', JSON.stringify(categoryNodeStatus));
}
//rename color
renameKeyConfig("sb_ColorCustomCategories", oldCategoryName, newCategoryName)
createContextualMenu();
reloadCtxMenu()
renderList("custom_categories_main");
closeModal()
} else {
alert("Category not found!");
}
}
async function readCategories() {
let categoryNodeMap = await getNodeMap();
if (categoryNodeMap) {
return Object.keys(categoryNodeMap);
}
else {
return [];
}
}
// Function to delete a category
async function deleteCategory(e) {
const name = e.target.dataset.namecategory;
confirmation = confirm("Are you sure you want to delete the category " + name + "?");
if (!confirmation) { return; }
let categoryNodeMap = await getNodeMap();
// If the category name is found, remove it from the array
if (categoryNodeMap[name]) {
removeNodeMapCategory(name);
createContextualMenu();
removeKeyFromConfig("sb_ColorCustomCategories", name)
setTimeout(() => {
renderList("custom_categories_main");
},200)
reloadCtxMenu()
} else {
alert("Category not found!");
}
}
/////////////////////////////////////////////////
async function getConfig(configName) {
let categoryNodeMap = JSON.parse(await getConfiguration(configName)) || {};
return categoryNodeMap;
}
async function getValueFromConfig(configName, key) {
let config = await getConfig(configName);
return config[key] || null;
}
async function assignValueToConfig(configName, key, value) {
let config = await getConfig(configName);
config[key] = value;
updateConfiguration(configName, JSON.stringify(config));
}
async function removeKeyFromConfig(configName, key) {
let config = await getConfig(configName);
delete config[key];
updateConfiguration(configName, JSON.stringify(config));
}
async function renameKeyConfig(configName, oldKey, newKey) {
let config = await getConfig(configName);
let value = config[oldKey];
delete config[oldKey];
config[newKey] = value;
updateConfiguration(configName, JSON.stringify(config));
}
async function removeKeyConfig(configName) {
removeConfiguration(configName)
renderList("custom_categories_main");
}
////////////////////////////////////////////////
async function getCategoriesForNode(nodeName) {
let categoryNodeMap = await getNodeMap();
let categories = [];
for (let category in categoryNodeMap) {
if (categoryNodeMap.hasOwnProperty(category)) {
if (categoryNodeMap[category].includes(nodeName)) {
categories.push(category);
}
}
}
return categories;
}
async function getNodesForCategory(categoryName) {
let categoryNodeMap = await getNodeMap();
return categoryNodeMap[categoryName] || [];
}
async function assignNodeToCategory(nodeName, categoryNames) {
let categoryNodeMap = await getNodeMap();
let assignedCategories = await getCategoriesForNode(nodeName);
categoryNames = categoryNames.filter(category => !assignedCategories.includes(category));
categoryNames.forEach(categoryName => {
if (!categoryNodeMap[categoryName]) {
categoryNodeMap[categoryName] = [];
}
categoryNodeMap[categoryName].push(nodeName);
});
updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap));
renderList("custom_categories_main");
}
async function removeNodeFromCategory(nodeName, categoryName) {
let categoryNodeMap = await getNodeMap();
if (categoryNodeMap[categoryName]) {
categoryNodeMap[categoryName] = categoryNodeMap[categoryName].filter(node => node !== nodeName);
updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap));
renderList("custom_categories_main");
}
}
async function removeNodeMapCategory(categoryName) {
let categoryNodeMap = await getNodeMap();
let categoryNodeStatus = getNodesStatus();
delete categoryNodeMap[categoryName];
updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap));
if (categoryNodeStatus[categoryName]) {
delete categoryNodeStatus[categoryName];
localStorage.setItem('sb_categoryNodeStatus', JSON.stringify(categoryNodeStatus));
}
}
function editNodeCategory(oldCategoryName, newCategoryName, nodeName) {
removeNodeFromCategory(nodeName, oldCategoryName);
assignNodeToCategory(nodeName, [newCategoryName]);
}
async function reorderCategories(orderedKeys) {
let categoryNodeMap = await getNodeMap();
let reorderedCategoryNodeMap = {};
orderedKeys.forEach(categoryKey => {
if (categoryNodeMap.hasOwnProperty(categoryKey)) {
reorderedCategoryNodeMap[categoryKey] = categoryNodeMap[categoryKey];
}
});
updateConfiguration('sb_categoryNodeMap', JSON.stringify(reorderedCategoryNodeMap));
}
async function reorderNodeInCategory(categoryName, orderedKeys) {
let categoryNodeMap = await getNodeMap();
if (categoryNodeMap[categoryName]) {
categoryNodeMap[categoryName] = orderedKeys;
updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap));
}
}
// Callbacks
function removeNodeFromCategoryCallback(e, trge) {
removeNodeFromCategory(e.target.dataset.type, e.target.parentElement.parentElement.dataset.namecategory)
}
function assignNodeToCategoryCallback(e, trge) {
assignNodeToCategory(e.target.id, [trge]);
}
function renameCategoryCallback(e, trge) {
const name = e.target.dataset.namecategory;
openModal(createCustomHtml("rename", name))
}
function colorCategoryCallback(e) {
const name = e.target.dataset.namecategory;
openModal(createCustomHtml("color", name, e.target))
//colorCategory(e.target.id, e.target.parentElement.parentElement.dataset.namecategory)
}
async function colorCategory(name, value) {
const value_perc = await getConfiguration("sb_opacity") || "1.0";
const rgbColor = hexToRgb(value);
currentElement.style.backgroundColor = `rgba(${rgbColor}, ${value_perc})`;
assignValueToConfig("sb_ColorCustomCategories", name, value)
}
// Add an event listener to the button to open the modal
function resetColorCategory(name) {
removeKeyFromConfig("sb_ColorCustomCategories", name)
renderList("custom_categories_main");
}
async function createContextualMenu() {
//const menuOptions = document.getElementById('menu-options');
new_menu_options = [];
new_submenu_options = [];
new_menu_options_callback = [];
new_submenu_options_callback = [];
new_menu_options.push("Add to category");
//for each category
category_menu = await readCategories();
callback_menu = [];
for (let i = 0; i < category_menu.length; i++) {
callback_menu.push("assignNodeToCategoryCallback");
}
new_submenu_options.push(category_menu);
new_submenu_options_callback.push(callback_menu);
console.log("custom category panel loaded")
}
function test_altert(e, trge) {
console.log(trge);
console.log(e.target.id);
console.log(e.target.parentElement.parentElement.dataset.namecategory);
assignNodeToCategory(e.target.id, [trge]);
}
const searchCategoryIconCustomCategory = document.querySelector(".searchCategoryIconCustomCategory");
searchCategoryIconCustomCategory.addEventListener("click", async function () {
try {
categorySearchToggleCustom = !categorySearchToggleCustom;
searchCategoryIconCustomCategory.style.opacity = categorySearchToggleCustom ? "1" : "0.5";
} catch (error) {
console.error("Error occurred during search:", error);
}
});
addSidebarStyles("panels/custom_categories/style.css");
createContextualMenu()
renderList("custom_categories_main");
+124
View File
@@ -0,0 +1,124 @@
#custom_categories_main,#custom_categories_header {
justify-content: center;
align-items: center;
height: 100%;
margin: 0;
}
#custom_categories_main{
padding-top: 44px;
}
#custom_categories_header {
margin: 10px 20px;
font-size: 16px;
}
#custom_categories_header #h_search {
position: absolute;
width: calc(100% - 74px);
margin-top: 0px;
z-index: 400;
}
#custom_categories_header button {
/* padding: 10px 20px; */
height: 35px;
width: 35px;
font-size: 16px;
background: var(--comfy-input-bg);
border: 2px solid var(--border-color);
color: var(--input-text);
border-radius: 5px;
float: right;
position: absolute;
margin: 2px 2px 2px 8px;
cursor: pointer;
}
#custom_categories_header button:hover {border: 2px solid var(--descrip-text);
}
.sb-category-dialog {
outline: 0;
border: 0;
border-radius: 6px;
background: var(--bg-color);
color: #fff;
box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0);
max-width: 800px;
min-width: 30rem;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-size: 1rem;
padding: 20px;
position: fixed;
top: 50%;
left: 50%;
z-index: 1001;
}
#sb-category-form {
margin-top: 10px;
text-align: center;
}
.sb-input {
width: 100%;
padding: 8px;
margin-bottom: 10px;
box-sizing: border-box;
border: 2px solid var(--border-color);
border-radius: 4px;
margin-top: 16px;
background: var(--bg-color);
color: var(--input-text);
}
.sb-input:focus {
outline: none;
border: 2px solid var(--descrip-text);
}
.sb-button {
background-color: var(--comfy-menu-bg);
color: var(--input-text);
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
text-transform: uppercase;
font-weight: bold;
}
.sb-button:hover {
filter: brightness(120%);
}
#closeModalButton {
background-color: var(--comfy-menu-bg);
position: absolute;
top: 0;
right: 0;
margin: 0;
}
#closeModalButton:hover {
filter: brightness(120%);
}
#searchInputCn {
box-sizing: border-box;
width: 100%;
border-radius: 5px;
padding: 10px;
border: none;
user-select: none;
background: var(--comfy-input-bg);
color: var(--input-text);
line-height: 1.4;
border: 1px solid var(--border-color);
}
+6
View File
@@ -0,0 +1,6 @@
[
{
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"id": "custom_view",
"icon": "⧉",
"description": "Custom Node Categories",
"panels": [
"custom_categories",
"custom_test"
]
}
]
+21
View File
@@ -0,0 +1,21 @@
<style>
#custom_categories {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
margin: 0;
}
#custom_categories button {
padding: 10px 20px;
font-size: 16px;
}
</style>
<div id="custom_categories" >
<button onclick="showAlert()">Cliccami</button>
</div>
+8
View File
@@ -0,0 +1,8 @@
//console.log(LiteGraph.registered_node_types);
//const menuOptions = document.getElementById('menu-options');
new_menu_options.push("Add to category");
new_submenu_options.push(["Category 1", "Category 2"]);
console.log("custom loaded")
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 31 KiB

-2016
View File
File diff suppressed because it is too large Load Diff