Skip to content

Commit

Permalink
feat: 🎨 Add an editor for the styles loader
Browse files Browse the repository at this point in the history
For simplicity I implemented it an endpoint for now.

Closes #84
  • Loading branch information
melMass committed Sep 2, 2023
1 parent 6a00d1d commit 167fa16
Show file tree
Hide file tree
Showing 8 changed files with 434 additions and 6 deletions.
24 changes: 24 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,29 @@ def load_nodes():
PromptServer.instance.app.router.add_static(
"/mtb-assets/", path=(here / "html").as_posix(), name="static"
)

@PromptServer.instance.routes.get("/mtb/manage")
async def manage(request):
from . import endpoint

reload(endpoint)

endlog.debug("Initializing Manager")
if "text/html" in request.headers.get("Accept", ""):
csv_editor = endpoint.csv_editor()

tabview = endpoint.render_tab_view(Styles=csv_editor)
return web.Response(
text=endpoint.render_base_template("MTB", tabview),
content_type="text/html",
)

return web.json_response(
{
"message": "manage only has a POST api for now",
}
)

@PromptServer.instance.routes.get("/mtb/status")
async def get_full_library(request):
from . import endpoint
Expand Down Expand Up @@ -258,6 +281,7 @@ async def get_home(request):
# # Return an HTML page
html_response = """
<div class="flex-container menu">
<a href="/mtb/manage">manage</a>
<a href="/mtb/debug">debug</a>
<a href="/mtb/status">status</a>
</div>
Expand Down
159 changes: 157 additions & 2 deletions endpoint.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from .utils import here, run_command, comfy_mode, import_install
from .utils import (
here,
import_install,
styles_dir,
backup_file,
)
from aiohttp import web
from .log import mklog
import sys
import csv


endlog = mklog("mtb endpoint")

Expand Down Expand Up @@ -49,6 +55,32 @@ def ACTIONS_getStyles(style_name=None):
return {"error": "No styles found"}


def ACTIONS_saveStyle(data):
# endlog.debug(f"Received Save Styles for {data.keys()}")
# endlog.debug(data)

styles = [f.name for f in styles_dir.iterdir() if f.suffix == ".csv"]
target = None
rows = []
for fp, content in data.items():
if fp in styles:
endlog.debug(f"Overwriting {fp}")
target = styles_dir / fp
rows = content
break

if not target:
endlog.warning(f"Could not determine the target file for {data.keys()}")
return {"error": "Could not determine the target file for the style"}

backup_file(target)

with target.open("w", newline="", encoding="utf-8") as file:
csv_writer = csv.writer(file, quoting=csv.QUOTE_ALL)
for row in rows:
csv_writer.writerow(row)


async def do_action(request) -> web.Response:
endlog.debug("Init action request")
request_data = await request.json()
Expand Down Expand Up @@ -84,6 +116,129 @@ def dependencies_button(name, dependencies):
"""


def csv_editor():
inputs = [f for f in styles_dir.iterdir() if f.suffix == ".csv"]
# rows = {f.stem: list(csv.reader(f.read_text("utf8"))) for f in styles}

style_files = {}
for file in inputs:
with open(file, "r", encoding="utf8") as f:
parsed = csv.reader(f)
style_files[file.name] = []
for row in parsed:
endlog.debug(f"Adding style {row[0]}")
style_files[file.name].append((row[0], row[1], row[2]))

html_out = """
<div id="style-editor">
<h1>Style Editor</h1>
"""
for current, styles in style_files.items():
current_out = f"<h3>{current}</h3>"
table_rows = []
for index, style in enumerate(styles):
table_rows += (
(["<tr>"] + [f"<th>{cell}</th>" for cell in style] + ["</tr>"])
if index == 0
else (
["<tr>"]
+ [
f"<td><input type='text' value='{cell}'></td>"
if i == 0
else f"<td><textarea name='Text1' cols='40' rows='5'>{cell}</textarea></td>"
for i, cell in enumerate(style)
]
+ ["</tr>"]
)
)
current_out += (
f"<table data-id='{current}' data-filename='{current}'>"
+ "".join(table_rows)
+ "</table>"
)
current_out += f"<button data-id='{current}' onclick='saveTableData(this.getAttribute(\"data-id\"))'>Save {current}</button>"

html_out += add_foldable_region(current, current_out)

html_out += "</div>"
html_out += """<script src='/mtb-assets/js/saveTableData.js'></script>"""

return html_out


def render_tab_view(**kwargs):
tab_headers = []
tab_contents = []

for idx, (tab_name, content) in enumerate(kwargs.items()):
active_class = "active" if idx == 0 else ""
tab_headers.append(
f"<button class='tablinks {active_class}' onclick=\"openTab(event, '{tab_name}')\">{tab_name}</button>"
)
tab_contents.append(
f"<div id='{tab_name}' class='tabcontent {active_class}'>{content}</div>"
)

headers_str = "\n".join(tab_headers)
contents_str = "\n".join(tab_contents)

return f"""
<div class='tab-container'>
<div class='tab'>
{headers_str}
</div>
{contents_str}
</div>
<script src='/mtb-assets/js/tabSwitch.js'></script>
"""


def add_foldable_region(title, content):
symbol_id = f"{title}-symbol"
return f"""
<div class='foldable'>
<div class='foldable-title' onclick="toggleFoldable('{title}', '{symbol_id}')">
<span id='{symbol_id}' class='foldable-symbol'>&#9655;</span>
{title}
</div>
<div id='{title}' class='foldable-content'>
{content}
</div>
</div>
<script src='/mtb-assets/js/foldable.js'></script>
"""


def add_split_pane(left_content, right_content, vertical=True):
orientation = "vertical" if vertical else "horizontal"
return f"""
<div class="split-pane {orientation}">
<div id="leftPane">
{left_content}
</div>
<div id="resizer"></div>
<div id="rightPane">
{right_content}
</div>
</div>
<script>
initSplitPane({str(vertical).lower()});
</script>
<script src='/mtb-assets/js/splitPane.js'></script>
"""


def add_dropdown(title, options):
option_str = "\n".join([f"<option value='{opt}'>{opt}</option>" for opt in options])
return f"""
<select>
<option disabled selected>{title}</option>
{option_str}
</select>
"""


def render_table(table_dict, sort=True, title=None):
table_dict = sorted(
table_dict.items(), key=lambda item: item[0]
Expand Down
20 changes: 20 additions & 0 deletions html/js/foldable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* File: foldable.js
* Project: comfy_mtb
* Author: Mel Massadian
*
* Copyright (c) 2023 Mel Massadian
*
*/

function toggleFoldable(elementId, symbolId) {
const content = document.getElementById(elementId)
const symbol = document.getElementById(symbolId)
if (content.style.display === 'none' || content.style.display === '') {
content.style.display = 'flex'
symbol.innerHTML = '&#9661;' // Down arrow
} else {
content.style.display = 'none'
symbol.innerHTML = '&#9655;' // Right arrow
}
}
54 changes: 54 additions & 0 deletions html/js/saveTableData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* File: saveTableData.js
* Project: comfy_mtb
* Author: Mel Massadian
*
* Copyright (c) 2023 Mel Massadian
*
*/

function saveTableData(identifier) {
const table = document.querySelector(
`#style-editor table[data-id='${identifier}']`
)

let currentData = []
const rows = table.querySelectorAll('tr')
const filename = table.getAttribute('data-id')

rows.forEach((row, rowIndex) => {
const rowData = []
const cells =
rowIndex === 0
? row.querySelectorAll('th')
: row.querySelectorAll('td input, td textarea')

cells.forEach((cell) => {
rowData.push(rowIndex === 0 ? cell.textContent : cell.value)
})

currentData.push(rowData)
})

let tablesData = {}
tablesData[filename] = currentData

console.debug('Sending styles to manage endpoint:', tablesData)
fetch('/mtb/actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'saveStyle',
args: tablesData,
}),
})
.then((response) => response.json())
.then((data) => {
console.debug('Success:', data)
})
.catch((error) => {
console.error('Error:', error)
})
}
34 changes: 34 additions & 0 deletions html/js/splitPane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* File: splitPane.js
* Project: comfy_mtb
* Author: Mel Massadian
*
* Copyright (c) 2023 Mel Massadian
*
*/

function initSplitPane(vertical) {
let resizer = document.getElementById('resizer')
let left = document.getElementById('leftPane')
let right = document.getElementById('rightPane')
resizer.addEventListener('mousedown', function (e) {
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', function () {
document.removeEventListener('mousemove', onMouseMove)
})
})

const onMouseMove = (e) => {
if (vertical) {
let leftWidth = e.clientX
let rightWidth = window.innerWidth - e.clientX
left.style.width = leftWidth + 'px'
right.style.width = rightWidth + 'px'
} else {
let topHeight = e.clientY
let bottomHeight = window.innerHeight - e.clientY
left.style.height = topHeight + 'px'
right.style.height = bottomHeight + 'px'
}
}
}
22 changes: 22 additions & 0 deletions html/js/tabSwitch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* File: tabSwitch.js
* Project: comfy_mtb
* Author: Mel Massadian
*
* Copyright (c) 2023 Mel Massadian
*
*/

function openTab(evt, tabName) {
var i, tabcontent, tablinks
tabcontent = document.getElementsByClassName('tabcontent')
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = 'none'
}
tablinks = document.getElementsByClassName('tablinks')
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(' active', '')
}
document.getElementById(tabName).style.display = 'block'
evt.currentTarget.className += ' active'
}
Loading

0 comments on commit 167fa16

Please sign in to comment.