first pass
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Python-specific
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Virtual environments
|
||||
.env/
|
||||
venv/
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# Other
|
||||
.DS_Store
|
||||
|
||||
# user junk
|
||||
_support/
|
||||
.coverage
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Alexander G. Morano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
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.
|
||||
|
||||
GO NUTS; JUST TRY NOT TO DO IT IN YOUR HEAD.
|
||||
@@ -0,0 +1,20 @@
|
||||
This project includes code concepts from the MTB Nodes project (MIT)
|
||||
https://github.com/melMass/comfy_mtb
|
||||
|
||||
This project includes code concepts from the ComfyUI-Custom-Scripts project (MIT)
|
||||
https://github.com/pythongosssss/ComfyUI-Custom-Scripts
|
||||
|
||||
This project includes code concepts from the KJNodes for ComfyUI project (GPL 3.0)
|
||||
https://github.com/kijai/ComfyUI-KJNodes
|
||||
|
||||
This project includes code concepts from the UE Nodes project (Apache 2.0)
|
||||
https://github.com/chrisgoringe/cg-use-everywhere
|
||||
|
||||
This project includes code concepts from the WAS Node Suite project (MIT)
|
||||
https://github.com/WASasquatch/was-node-suite-comfyui
|
||||
|
||||
This project includes code concepts from the rgthree-comfy project (MIT)
|
||||
https://github.com/rgthree/rgthree-comfy
|
||||
|
||||
This project includes code concepts from the FizzNodes project (MIT)
|
||||
https://github.com/FizzleDorf/ComfyUI_FizzNodes
|
||||
@@ -1,2 +1,70 @@
|
||||
# Jovi_Colorizer
|
||||
Colorize nodes in comfy with custom defaults per node, node category or through regex filtering
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="https://raw.githubusercontent.com/Amorano/Jovimetrix-examples/refs/heads/master/res/logo-jovi_colorizer.png">
|
||||
<img alt="Colorize ComfyUI nodes with defaults per node, node category or via regex filtering " width="256" height="256">
|
||||
</picture>
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
<a href="https://github.com/comfyanonymous/ComfyUI">COMFYUI</a> Colorize ComfyUI nodes with defaults per node, node category or via regex filtering
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
<!---------------------------------------------------------------------------->
|
||||
|
||||
# SPONSORSHIP
|
||||
|
||||
Please consider sponsoring me if you enjoy the results of my work, code or documentation or otherwise. A good way to keep code development open and free is through sponsorship.
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | |
|
||||
-|-|-|-
|
||||
[](https://github.com/sponsors/Amorano) | [](https://www.paypal.com/paypalme/onarom) | [](https://www.patreon.com/joviex) | [](https://ko-fi.com/alexandermorano)
|
||||
</div>
|
||||
|
||||
## HIGHLIGHTS
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/user-attachments/assets/cc7a6483-401b-4049-9645-6f57a940fb70" alt="Jovi Streaming Node Family" width="768"/>
|
||||
</div>
|
||||
|
||||
*
|
||||
|
||||
## UPDATES
|
||||
|
||||
**2025/03/02** @1.0.0:
|
||||
* intial breakout from Jovimetrix
|
||||
|
||||
# INSTALLATION
|
||||
|
||||
## COMFYUI MANAGER
|
||||
|
||||
If you have [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) installed, simply search for Jovi_Colorizer and install from the manager's database.
|
||||
|
||||
## MANUAL INSTALL
|
||||
Clone the repository into your ComfyUI custom_nodes directory. You can clone the repository with the command:
|
||||
```
|
||||
git clone https://github.com/Amorano/Jovi_Colorizer.git
|
||||
```
|
||||
You can then install the requirements by using the command:
|
||||
```
|
||||
.\python_embed\python.exe -s -m pip install -r requirements.txt
|
||||
```
|
||||
If you are using a <code>virtual environment</code> (<code><i>venv</i></code>), make sure it is activated before installation. Then install the requirements with the command:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
# WHERE TO FIND ME
|
||||
|
||||
You can find me on [](https://discord.gg/62TJaZ3Z5r).
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
██╗ ██████╗ ██╗ ██╗██╗
|
||||
██║██╔═══██╗██║ ██║██║
|
||||
██║██║ ██║██║ ██║██║
|
||||
██ ██║██║ ██║╚██╗ ██╔╝██║
|
||||
╚█████╔╝╚██████╔╝ ╚████╔╝ ██║
|
||||
╚════╝ ╚═════╝ ╚═══╝ ╚═╝
|
||||
|
||||
██████╗ ██████╗ ██╗ ██████╗ ██████╗ ██╗███████╗███████╗██████╗
|
||||
██╔════╝██╔═══██╗██║ ██╔═══██╗██╔══██╗██║╚══███╔╝██╔════╝██╔══██╗
|
||||
██║ ██║ ██║██║ ██║ ██║██████╔╝██║ ███╔╝ █████╗ ██████╔╝
|
||||
██║ ██║ ██║██║ ██║ ██║██╔══██╗██║ ███╔╝ ██╔══╝ ██╔══██╗
|
||||
╚██████╗╚██████╔╝███████╗╚██████╔╝██║ ██║██║███████╗███████╗██║ ██║
|
||||
╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
|
||||
|
||||
Colorize ComfyUI nodes with defaults per node, node category or via regex filtering
|
||||
http://www.github.com/amorano/Jovi_Colorizer
|
||||
"""
|
||||
|
||||
__author__ = "Alexander G. Morano"
|
||||
__email__ = "amorano@gmail.com"
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = {}, {}
|
||||
@@ -0,0 +1,29 @@
|
||||
[project]
|
||||
name = "jovi_colorizer"
|
||||
description = "Colorize ComfyUI nodes with defaults per node, node category or via regex filtering."
|
||||
version = "1.0.0"
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
authors = [{ name = "Alexander G. Morano", email = "amorano@gmail.com" }]
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Intended Audience :: Developers",
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/Amorano/Jovi_Colorizer"
|
||||
Documentation = "https://github.com/Amorano/Jovi_Colorizer/wiki"
|
||||
Repository = "https://github.com/Amorano/Jovi_Colorizer"
|
||||
Issues = "https://github.com/Amorano/Jovi_Colorizer/issues"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "amorano"
|
||||
DisplayName = "Jovi Colorizer"
|
||||
Icon = "https://raw.githubusercontent.com/Amorano/Jovimetrix-examples/refs/heads/master/res/logo-jovi_colorizer.png"
|
||||
@@ -0,0 +1,639 @@
|
||||
/**
|
||||
* File: core_color.js
|
||||
*/
|
||||
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js"
|
||||
import { $el } from "../../scripts/ui.js";
|
||||
|
||||
const DEFAULT_THEME = {
|
||||
"JOV_CAPTURE 📸": { title: "#661f66" },
|
||||
"JOV_GL 🌈": { title: "#1f661f" },
|
||||
"JOV_MEASURE 📐": { title: "#993838" },
|
||||
"JOV_MIDI 🎛️": { title: "#66661f" },
|
||||
"JOV_SPOUT 📺": { title: "#1f6666" },
|
||||
"JOVIMETRIX 🔺🟩🔵": { title: "#a23da2" },
|
||||
"JOVIMETRIX 🔺🟩🔵/CREATE": { title: "#1b871b" },
|
||||
"JOVIMETRIX 🔺🟩🔵/COMPOSE": { title: "#5c1f9a" },
|
||||
"JOVIMETRIX 🔺🟩🔵/CALC": { title: "#993838" },
|
||||
"JOVIMETRIX 🔺🟩🔵/DEVICE": { title: "#1f9999" },
|
||||
"JOVIMETRIX 🔺🟩🔵/UTILITY": { title: "#0a0a0a" }
|
||||
};
|
||||
|
||||
const SETTING_REGEX = 'jovi.color.regex';
|
||||
const SETTING_THEME = 'jovi.color.theme';
|
||||
|
||||
let PANEL_COLORIZE, NODE_LIST;
|
||||
|
||||
function normalizeHex(hex) {
|
||||
if (!hex.startsWith('#')) {
|
||||
hex = '#' + hex;
|
||||
}
|
||||
|
||||
// If shorthand (e.g., #333), expand it to full form (#333333)
|
||||
if (hex.length === 4) {
|
||||
hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
||||
}
|
||||
return hex.toLowerCase();
|
||||
}
|
||||
|
||||
function colorHex2RGB(hex) {
|
||||
hex = hex.replace(/^#/, '');
|
||||
const bigint = parseInt(hex, 16);
|
||||
const r = (bigint >> 16) & 255;
|
||||
const g = (bigint >> 8) & 255;
|
||||
const b = bigint & 255;
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
function colorContrast(hexColor) {
|
||||
const rgb = colorHex2RGB(hexColor);
|
||||
const normalizedRGB = rgb.map(value => value / 255);
|
||||
const L = 0.2126 * normalizedRGB[0] + 0.7152 * normalizedRGB[1] + 0.0722 * normalizedRGB[2];
|
||||
return L > 0.420 ? "#000" : "#CCC";
|
||||
}
|
||||
|
||||
function getColor(node) {
|
||||
// regex overrides first
|
||||
const CONFIG_REGEX = app.extensionManager.setting.get(SETTING_REGEX);
|
||||
if (Array.isArray(CONFIG_REGEX)) {
|
||||
for (const { regex, ...colors } of CONFIG_REGEX) {
|
||||
if (regex && node.type.match(new RegExp(regex, "i"))) {
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// explicit color set first...
|
||||
const CONFIG_THEME = app.extensionManager.setting.get(SETTING_THEME);
|
||||
const newColor = CONFIG_THEME?.[node.type]
|
||||
?? (function() {
|
||||
const color = NODE_LIST[node.type];
|
||||
let k = color?.category;
|
||||
while (k) {
|
||||
if (CONFIG_THEME?.[k]) {
|
||||
return CONFIG_THEME[k];
|
||||
}
|
||||
k = k.substring(0, k.lastIndexOf("/"));
|
||||
}
|
||||
})();
|
||||
|
||||
return newColor;
|
||||
}
|
||||
|
||||
const origDrawNode = LGraphCanvas.prototype.drawNode;
|
||||
LGraphCanvas.prototype.drawNode = function (node, ctx) {
|
||||
// STASH THE CURRENT COLOR STATE
|
||||
const origTitle = node.constructor.title_text_color;
|
||||
const origSelectedTitleColor = LiteGraph.NODE_SELECTED_TITLE_COLOR;
|
||||
const origNodeTextColor = LiteGraph.NODE_TEXT_COLOR;
|
||||
const origWidgetSecondaryTextColor = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
||||
const origWidgetTextColor = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
const origNodeTitleColor = LiteGraph.NODE_TITLE_COLOR;
|
||||
const origWidgetBGColor = LiteGraph.WIDGET_BGCOLOR;
|
||||
|
||||
const new_color = getColor(node);
|
||||
|
||||
if (new_color) {
|
||||
// Title text when node is selected
|
||||
//LiteGraph.NODE_SELECTED_TITLE_COLOR = '#FF00FF'
|
||||
|
||||
if (new_color?.text) {
|
||||
node.constructor.title_text_color = new_color.text;
|
||||
} else {
|
||||
const color = node.constructor.title_text_color;
|
||||
if (color) {
|
||||
node.constructor.title_text_color = colorContrast(color);
|
||||
}
|
||||
}
|
||||
|
||||
// Slot label text
|
||||
//LiteGraph.NODE_TEXT_COLOR = '#7777FF'
|
||||
|
||||
// Widget Text
|
||||
//LiteGraph.WIDGET_SECONDARY_TEXT_COLOR = "#FFFFFF"
|
||||
|
||||
// Widget controls + field text
|
||||
//LiteGraph.WIDGET_TEXT_COLOR = '#FF0000';
|
||||
|
||||
// Widget control BG color
|
||||
// LiteGraph.WIDGET_BGCOLOR
|
||||
|
||||
// node's title bar background color
|
||||
if (new_color?.title) {
|
||||
node.color = new_color.title;
|
||||
}
|
||||
|
||||
// node's body background color
|
||||
if (new_color?.body) {
|
||||
node.bgcolor = new_color.body;
|
||||
}
|
||||
}
|
||||
|
||||
const res = origDrawNode.apply(this, arguments);
|
||||
|
||||
// Default back to last pushed state ComfyUI colors
|
||||
if (new_color) {
|
||||
node.constructor.title_text_color = origTitle;
|
||||
LiteGraph.NODE_SELECTED_TITLE_COLOR = origSelectedTitleColor;
|
||||
LiteGraph.NODE_TEXT_COLOR = origNodeTextColor;
|
||||
LiteGraph.WIDGET_SECONDARY_TEXT_COLOR = origWidgetSecondaryTextColor;
|
||||
LiteGraph.WIDGET_TEXT_COLOR = origWidgetTextColor;
|
||||
LiteGraph.NODE_TITLE_COLOR = origNodeTitleColor;
|
||||
LiteGraph.WIDGET_BGCOLOR = origWidgetBGColor;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
const majorElements = document.querySelectorAll('.jov-panel-color-cat_major');
|
||||
const minorElements = document.querySelectorAll('.jov-panel-color-cat_minor');
|
||||
majorElements.forEach(el => el.classList.remove('light', 'dark'));
|
||||
minorElements.forEach(el => el.classList.remove('light', 'dark'));
|
||||
majorElements.forEach(el => el.classList.add(theme));
|
||||
minorElements.forEach(el => el.classList.add(theme));
|
||||
}
|
||||
|
||||
class JovimetrixPanelColorize {
|
||||
constructor() {
|
||||
this.content = null;
|
||||
this.picker = null;
|
||||
this.pickerWrapper = null;
|
||||
this.buttonCurrent = null;
|
||||
this.recentColors = [];
|
||||
this.title_content = "HI!"
|
||||
this.searchInput = null;
|
||||
this.tbody = null;
|
||||
}
|
||||
|
||||
createSearchInput() {
|
||||
this.searchInput = $el("input", {
|
||||
type: "text",
|
||||
placeholder: "Filter nodes...",
|
||||
className: "jov-search-input",
|
||||
oninput: (e) => this.filterItems(e.target.value)
|
||||
});
|
||||
return this.searchInput;
|
||||
}
|
||||
|
||||
filterItems(searchTerm) {
|
||||
if (!this.tbody) return;
|
||||
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const rows = this.tbody.querySelectorAll('tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const nameCell = row.querySelector('td:last-child');
|
||||
if (!nameCell) return;
|
||||
|
||||
const text = nameCell.textContent.toLowerCase();
|
||||
const categoryMatch = row.classList.contains('jov-panel-color-cat_major') ||
|
||||
row.classList.contains('jov-panel-color-cat_minor');
|
||||
|
||||
// Show categories if they or their children match
|
||||
if (categoryMatch) {
|
||||
const siblingRows = this.getNextSiblingRowsUntilCategory(row);
|
||||
const hasVisibleChildren = siblingRows.some(sibling => {
|
||||
const siblingText = sibling.querySelector('td:last-child')?.textContent.toLowerCase() || '';
|
||||
return siblingText.includes(searchLower);
|
||||
});
|
||||
|
||||
row.style.display = hasVisibleChildren || text.includes(searchLower) ? '' : 'none';
|
||||
} else {
|
||||
row.style.display = text.includes(searchLower) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getNextSiblingRowsUntilCategory(categoryRow) {
|
||||
const siblings = [];
|
||||
let currentRow = categoryRow.nextElementSibling;
|
||||
|
||||
while (currentRow &&
|
||||
!currentRow.classList.contains('jov-panel-color-cat_major') &&
|
||||
!currentRow.classList.contains('jov-panel-color-cat_minor')) {
|
||||
siblings.push(currentRow);
|
||||
currentRow = currentRow.nextElementSibling;
|
||||
}
|
||||
|
||||
return siblings;
|
||||
}
|
||||
|
||||
createColorButton(type, name, color, idx) {
|
||||
const label = type == "title" ? "T" : type == "body" ? "B" : "X";
|
||||
const button = $el('button.color-button', {
|
||||
style: { backgroundColor: color },
|
||||
dataset: {
|
||||
type: type,
|
||||
name: name,
|
||||
color: color,
|
||||
colorOld: color,
|
||||
idx: idx
|
||||
},
|
||||
value: label,
|
||||
content: label,
|
||||
textContent: label,
|
||||
label: label
|
||||
});
|
||||
|
||||
button.addEventListener('mousedown', (event) => {
|
||||
event.stopPropagation();
|
||||
if (this.buttonCurrent) {
|
||||
this.buttonCurrent.dataset.colorOld = normalizeHex(this.buttonCurrent.dataset.color);
|
||||
}
|
||||
this.buttonCurrent = event.target;
|
||||
this.showPicker(event.target);
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
async updateConfig() {
|
||||
const cb = this.buttonCurrent.dataset;
|
||||
const color = normalizeHex(cb.color)
|
||||
if (cb.idx && cb.idx !== "undefined") {
|
||||
const CONFIG_REGEX = app.extensionManager.setting.get(SETTING_REGEX);
|
||||
CONFIG_REGEX[cb.idx][cb.type] = color;
|
||||
await app.extensionManager.setting.set(SETTING_REGEX, CONFIG_REGEX);
|
||||
} else {
|
||||
const CONFIG_THEME = app.extensionManager.setting.get(SETTING_THEME);
|
||||
CONFIG_THEME[cb.name] = CONFIG_THEME[cb.name] || (CONFIG_THEME[cb.name] = {});
|
||||
|
||||
let colorCheck = LiteGraph.NODE_DEFAULT_BGCOLOR;
|
||||
if (cb.type === "title") {
|
||||
colorCheck = LiteGraph.NODE_DEFAULT_COLOR;
|
||||
} else if (cb.type === "text") {
|
||||
colorCheck = LiteGraph.NODE_TEXT_COLOR;
|
||||
}
|
||||
colorCheck = normalizeHex(colorCheck);
|
||||
if (color === colorCheck && CONFIG_THEME[cb.name] && CONFIG_THEME[cb.name].hasOwnProperty(cb.type)) {
|
||||
delete CONFIG_THEME[cb.name][cb.type];
|
||||
} else if (color !== colorCheck) {
|
||||
CONFIG_THEME[cb.name][cb.type] = color;
|
||||
}
|
||||
await app.extensionManager.setting.set(SETTING_THEME, CONFIG_THEME);
|
||||
}
|
||||
|
||||
this.buttonCurrent.style.backgroundColor = color;
|
||||
app.canvas.setDirty(true);
|
||||
}
|
||||
|
||||
async pickerCancel() {
|
||||
this.buttonCurrent.dataset.colorOld = normalizeHex(this.buttonCurrent.dataset.colorOld);
|
||||
this.buttonCurrent.dataset.color = this.buttonCurrent.dataset.colorOld;
|
||||
this.pickerWrapper.style.display = 'none';
|
||||
await this.updateConfig();
|
||||
this.buttonCurrent = null;
|
||||
}
|
||||
|
||||
async pickerReset() {
|
||||
let colorCheck = LiteGraph.NODE_DEFAULT_BGCOLOR;
|
||||
if (this.buttonCurrent.dataset.type === "title") {
|
||||
colorCheck = LiteGraph.NODE_DEFAULT_COLOR;
|
||||
} else if (this.buttonCurrent.dataset.type === "text") {
|
||||
colorCheck = LiteGraph.NODE_TEXT_COLOR;
|
||||
}
|
||||
this.buttonCurrent.dataset.color = colorCheck;
|
||||
await this.updateConfig();
|
||||
await this.picker.color.set(colorCheck);
|
||||
}
|
||||
|
||||
async pickerColorChange(color) {
|
||||
this.buttonCurrent.style.backgroundColor = color;
|
||||
this.buttonCurrent.dataset.color = color;
|
||||
await this.updateConfig();
|
||||
}
|
||||
|
||||
showPicker(button) {
|
||||
if (!this.picker) {
|
||||
|
||||
this.pickerWrapper = $el('div.picker-wrapper', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
zIndex: '9999',
|
||||
backgroundColor: '#fff',
|
||||
padding: '5px',
|
||||
borderRadius: '5px',
|
||||
boxShadow: '0 0 10px rgba(0,0,0,0.2)',
|
||||
display: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const pickerElement = $el('div.picker');
|
||||
const recentColorsElement = $el('div.recent-colors');
|
||||
const buttonWrapper = $el('div.button-wrapper', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: '10px'
|
||||
}
|
||||
});
|
||||
const cancelButton = $el('button', {
|
||||
textContent: 'Cancel',
|
||||
onclick: async () => this.pickerCancel()
|
||||
});
|
||||
|
||||
const resetButton = $el('button', {
|
||||
textContent: 'Reset',
|
||||
onclick: async () => this.pickerReset()
|
||||
});
|
||||
|
||||
buttonWrapper.appendChild(cancelButton);
|
||||
buttonWrapper.appendChild(resetButton);
|
||||
|
||||
this.pickerWrapper.appendChild(pickerElement);
|
||||
this.pickerWrapper.appendChild(recentColorsElement);
|
||||
this.pickerWrapper.appendChild(buttonWrapper);
|
||||
|
||||
document.body.appendChild(this.pickerWrapper);
|
||||
|
||||
this.picker = new iro.ColorPicker(pickerElement, {
|
||||
width: 200,
|
||||
color: '#ffffff',
|
||||
display: 'block',
|
||||
layout: [
|
||||
{
|
||||
component: iro.ui.Slider,
|
||||
options: { sliderType: 'hue' }
|
||||
},
|
||||
{
|
||||
component: iro.ui.Slider,
|
||||
options: { sliderType: 'value' }
|
||||
},
|
||||
{
|
||||
component: iro.ui.Slider,
|
||||
options: { sliderType: 'saturation' }
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
this.picker.on('color:change', async (color) => this.pickerColorChange(color.hexString));
|
||||
}
|
||||
|
||||
this.picker.color.set(button.dataset.color || '#ffffff');
|
||||
const buttonRect = button.getBoundingClientRect();
|
||||
const pickerRect = this.pickerWrapper.getBoundingClientRect();
|
||||
|
||||
let left = buttonRect.left;
|
||||
let top = buttonRect.bottom + 5;
|
||||
|
||||
if (left + pickerRect.width > window.innerWidth) {
|
||||
left = window.innerWidth - pickerRect.width - 5;
|
||||
}
|
||||
|
||||
if (top + pickerRect.height > window.innerHeight) {
|
||||
top = buttonRect.top - pickerRect.height - 5;
|
||||
}
|
||||
|
||||
this.pickerWrapper.style.left = `${left}px`;
|
||||
this.pickerWrapper.style.top = `${top}px`;
|
||||
this.pickerWrapper.style.display = 'block';
|
||||
this.updateRecentColors(button);
|
||||
}
|
||||
|
||||
updateRecentColors(button) {
|
||||
const color = button.dataset.color;
|
||||
if (!this.recentColors.includes(color)) {
|
||||
this.recentColors.unshift(color);
|
||||
if (this.recentColors.length > 5) {
|
||||
this.recentColors.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const recentColorsElement = this.pickerWrapper.querySelector('.recent-colors');
|
||||
recentColorsElement.innerHTML = '';
|
||||
this.recentColors.forEach(recentColor => {
|
||||
const colorSwatch = $el('div', {
|
||||
style: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
backgroundColor: recentColor,
|
||||
display: 'inline-block',
|
||||
margin: '2px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
onclick: () => this.picker.color.set(recentColor)
|
||||
});
|
||||
recentColorsElement.appendChild(colorSwatch);
|
||||
});
|
||||
}
|
||||
|
||||
templateColorRow(data, type, classList = "jov-panel-color-category") {
|
||||
const titleColor = data.title || LiteGraph.NODE_DEFAULT_COLOR;
|
||||
const bodyColor = data.body || LiteGraph.NODE_DEFAULT_BGCOLOR;
|
||||
const textColor = data.text || LiteGraph.NODE_TEXT_COLOR;
|
||||
|
||||
// Determine background color based on class
|
||||
let rowClass = classList;
|
||||
let style = {};
|
||||
|
||||
if (classList === "jov-panel-color-cat_major") {
|
||||
// Darker background for major categories
|
||||
style.backgroundColor = "var(--border-color)";
|
||||
} else if (classList === "jov-panel-color-cat_minor") {
|
||||
style.backgroundColor = "var(--tr-odd-bg-color)";
|
||||
}
|
||||
|
||||
const element = $el("tr", { className: rowClass, style }, [
|
||||
$el("td", {}, [this.createColorButton("title", data.name, titleColor, data.idx)]),
|
||||
$el("td", {}, [this.createColorButton("body", data.name, bodyColor, data.idx)]),
|
||||
$el("td", {}, [this.createColorButton("text", data.name, textColor, data.idx)]),
|
||||
(type === "regex") ? $el("td", [
|
||||
$el("input", {
|
||||
value: data.name
|
||||
})
|
||||
])
|
||||
: $el("td", { textContent: data.name })
|
||||
]);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
createRegexPalettes() {
|
||||
const table = $el("table.flexible-table");
|
||||
this.tbody = $el("tbody");
|
||||
|
||||
let CONFIG_REGEX = app.extensionManager.setting.get(SETTING_REGEX) || [];
|
||||
if (!Array.isArray(CONFIG_REGEX)) {
|
||||
CONFIG_REGEX = []
|
||||
}
|
||||
CONFIG_REGEX.forEach((entry, idx) => {
|
||||
const data = {
|
||||
idx: idx,
|
||||
name: entry.regex,
|
||||
title: entry.title || LiteGraph.NODE_TITLE_COLOR,
|
||||
body: entry.body || LiteGraph.WIDGET_BGCOLOR,
|
||||
text: entry.body || LiteGraph.NODE_TEXT_COLOR
|
||||
};
|
||||
this.tbody.appendChild(this.templateColorRow(data, "regex"));
|
||||
});
|
||||
|
||||
table.appendChild(this.tbody);
|
||||
return table;
|
||||
}
|
||||
|
||||
createColorPalettes() {
|
||||
let background_index = 0;
|
||||
const categories = [];
|
||||
const CONFIG_THEME = app.extensionManager.setting.get(SETTING_THEME);
|
||||
|
||||
Object.entries(NODE_LIST).forEach(([nodeName, node]) => {
|
||||
const category = node.category;
|
||||
const majorCategory = category.split("/")?.[0];
|
||||
|
||||
if (!categories.includes(majorCategory)) {
|
||||
background_index = (background_index + 1) % 2;
|
||||
const element = {
|
||||
name: majorCategory,
|
||||
title: CONFIG_THEME?.[majorCategory]?.title,
|
||||
body: CONFIG_THEME?.[majorCategory]?.body,
|
||||
text: CONFIG_THEME?.[majorCategory]?.text
|
||||
};
|
||||
this.tbody.appendChild(this.templateColorRow(element, null, "jov-panel-color-cat_major"));
|
||||
categories.push(majorCategory);
|
||||
}
|
||||
|
||||
if (!categories.includes(category)) {
|
||||
background_index = (background_index + 1) % 2;
|
||||
const element = {
|
||||
name: category,
|
||||
title: CONFIG_THEME?.[category]?.title,
|
||||
body: CONFIG_THEME?.[category]?.body,
|
||||
text: CONFIG_THEME?.[category]?.text
|
||||
};
|
||||
this.tbody.appendChild(this.templateColorRow(element, null, "jov-panel-color-cat_minor"));
|
||||
categories.push(category);
|
||||
}
|
||||
|
||||
const nodeConfig = CONFIG_THEME[nodeName] || {};
|
||||
const data = {
|
||||
name: nodeName,
|
||||
title: nodeConfig.title,
|
||||
body: nodeConfig.body,
|
||||
text: nodeConfig.text
|
||||
};
|
||||
this.tbody.appendChild(this.templateColorRow(data));
|
||||
});
|
||||
}
|
||||
|
||||
getRandomTitle() {
|
||||
const TITLES = [
|
||||
"COLOR CONFIGURATION", "COLOR CALIBRATION", "COLOR CUSTOMIZATION",
|
||||
"CHROMA CALIBRATION", "CHROMA CONFIGURATION", "CHROMA CUSTOMIZATION",
|
||||
"CHROMATIC CALIBRATION", "CHROMATIC CONFIGURATION", "CHROMATIC CUSTOMIZATION",
|
||||
"HUE HOMESTEAD", "PALETTE PREFERENCES", "PALETTE PERSONALIZATION",
|
||||
"PALETTE PICKER", "PIGMENT PREFERENCES", "PIGMENT PERSONALIZATION",
|
||||
"PIGMENT PICKER", "SPECTRUM STYLING", "TINT TAILORING", "TINT TWEAKING"
|
||||
];
|
||||
return TITLES[Math.floor(Math.random() * TITLES.length)];
|
||||
}
|
||||
|
||||
createContent() {
|
||||
if (!this.content) {
|
||||
const table = this.createRegexPalettes();
|
||||
this.createColorPalettes();
|
||||
|
||||
this.title_content = $el("div.jov-title-header", { textContent: "EMPTY" });
|
||||
this.content = $el("div.jov-panel-color", [
|
||||
$el("div.jov-title", [this.title_content]),
|
||||
this.createSearchInput(), // Add search input
|
||||
$el("div.jov-config-color", [table]),
|
||||
$el("div.button", []),
|
||||
]);
|
||||
|
||||
// hide the picker when clicking outside
|
||||
document.addEventListener('click', async (event) => {
|
||||
if (this.picker && !this.pickerWrapper.contains(event.target) && !event.target.classList.contains('color-button')) {
|
||||
this.pickerWrapper.style.display = 'none';
|
||||
if (this.buttonCurrent) {
|
||||
this.buttonCurrent.dataset.colorOld = normalizeHex(this.buttonCurrent.dataset.color);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
applyTheme('light');
|
||||
this.title_content.textContent = this.getRandomTitle();
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "jovimetrix.sidebar.colorizer",
|
||||
icon: "pi pi-palette",
|
||||
title: "JOVIMETRIX COLORIZER 🔺🟩🔵",
|
||||
tooltip: "Color node title and body via unique name, group and regex filtering\nJOVIMETRIX 🔺🟩🔵",
|
||||
type: "custom",
|
||||
render: async (el) => {
|
||||
el.innerHTML = "";
|
||||
if (!PANEL_COLORIZE) {
|
||||
PANEL_COLORIZE = new JovimetrixPanelColorize();
|
||||
}
|
||||
const content = PANEL_COLORIZE.createContent();
|
||||
el.appendChild(content);
|
||||
}
|
||||
});
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.color",
|
||||
settings: [
|
||||
{
|
||||
id: SETTING_REGEX,
|
||||
name: "Regex Entries for Jovimetrix Colorizer",
|
||||
type: "hidden",
|
||||
defaultValue: {}
|
||||
},
|
||||
{
|
||||
id: SETTING_THEME,
|
||||
name: "Node theme entries for Jovimetrix Colorizer",
|
||||
type: "hidden",
|
||||
defaultValue: {}
|
||||
},
|
||||
],
|
||||
async init() {
|
||||
document.head.appendChild(Object.assign(document.createElement('script'), {
|
||||
src: "https://cdn.jsdelivr.net/npm/@jaames/iro@5"
|
||||
}));
|
||||
},
|
||||
async setup() {
|
||||
|
||||
var response = await api.fetchApi("/object_info", { cache: "no-store" });
|
||||
const all_nodes = await response.json();
|
||||
|
||||
NODE_LIST = Object.entries(all_nodes).sort((a, b) => {
|
||||
const categoryA = a[1].category.toLowerCase();
|
||||
const categoryB = b[1].category.toLowerCase();
|
||||
|
||||
// First, sort by category
|
||||
if (categoryA < categoryB) return -1;
|
||||
if (categoryA > categoryB) return 1;
|
||||
|
||||
// If categories are equal, sort by key name
|
||||
return a[0].toLowerCase().localeCompare(b[0].toLowerCase());
|
||||
});
|
||||
NODE_LIST = Object.fromEntries(NODE_LIST);
|
||||
|
||||
let CONFIG_REGEX = app.extensionManager.setting.get(SETTING_REGEX) || [];
|
||||
if (!Array.isArray(CONFIG_REGEX)) {
|
||||
CONFIG_REGEX = [];
|
||||
}
|
||||
while (CONFIG_REGEX.length < 5) {
|
||||
CONFIG_REGEX.push({ "regex": "" });
|
||||
}
|
||||
await app.extensionManager.setting.set(SETTING_REGEX, CONFIG_REGEX);
|
||||
|
||||
let CONFIG_THEME = app.extensionManager.setting.get(SETTING_THEME) || {};
|
||||
Object.keys(DEFAULT_THEME).forEach(key => {
|
||||
if (!(key in CONFIG_THEME)) {
|
||||
CONFIG_THEME[key] = DEFAULT_THEME[key];
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await app.extensionManager.setting.set(SETTING_THEME, CONFIG_THEME);
|
||||
console.info("wrote new Jovi_Colorizer defaults")
|
||||
} catch (error) {
|
||||
console.error("Failed to update settings:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user