update PainterNode

+ Add workflow name to metdata saved to state.
+ Updated the visual appearance of asset display.
This commit is contained in:
AlekPet
2026-09-27 21:13:04 +03:00
parent 2ee14a8708
commit add7a5dca2
4 changed files with 117 additions and 16 deletions
+13
View File
@@ -61,6 +61,12 @@ async def list_painter_assets(request):
try:
with open(state_path, "r", encoding="utf-8") as f:
state = json.load(f)
metadata = state.get("metadata")
if not metadata:
metadata = {}
workflow_name = metadata.get("workflow_name", "Unsaved Workflow")
size = state.get("settings", {}).get("currentCanvasSize", {})
width = size.get("width")
height = size.get("height")
@@ -74,6 +80,7 @@ async def list_painter_assets(request):
"updated_at": stat.st_mtime,
"width": width,
"height": height,
"workflow_name": workflow_name
}
)
@@ -115,10 +122,16 @@ async def save_painter_asset(request):
asset_dir = _asset_dir(asset_id)
payload = await request.json()
state = payload.get("state")
workflow_name = payload.get("workflow_name")
if not isinstance(state, dict):
return _json_response_error("state must be an object", 400)
if not hasattr(state, "metadata"):
state.setdefault("metadata", {})
state["metadata"]["workflow_name"] = workflow_name
os.makedirs(asset_dir, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(
prefix=".state-",
@@ -513,6 +513,7 @@
}
.alekpet_painter_storage_item_card {
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
@@ -531,11 +532,58 @@
background: #000;
}
.alekpet_painter_storage_item_id_text {
.alekpet_painter_storage_button_close {
position: absolute;
top: 8px;
right: 8px;
transition: all 0.4s;
border: 0;
border-radius: 0 0 0 50%;
background: var(--comfy-menu-bg);
cursor: pointer;
}
.alekpet_painter_storage_button_close:hover {
border-radius: 50% 50% 50% 0;
padding: 2px;
border: 1px solid rgb(255 151 0);
background: rgb(255 151 0 / 80%);
}
.alekpet_painter_storage_item_meta {
font-size: 11px;
display: flex;
flex-direction: column;
gap: 3px;
flex: 1 1 0;
}
.alekpet_painter_storage_item_id {
font-family: monospace;
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.alekpet_painter_storage_item_skew_box {
background: linear-gradient(90deg, #727272, transparent);
padding: 3px 0px;
flex-direction: column;
clip-path: polygon(0px 0%, 100% 0%, calc(100% - 15px) 100%, 0% 100%);
}
.alekpet_painter_storage_item_size {
background: linear-gradient(90deg, #14377a, transparent);
}
.alekpet_painter_storage_item_size > span {
color: var(----success-color);
}
.alekpet_painter_storage_item_workflow_name {
background: linear-gradient(90deg, #096e00, transparent);
}
.alekpet_painter_storage_item_workflow_name > span {
color: var(--success-color);
}
/* -- end Settings store managing data */
@@ -118,27 +118,48 @@ export class PainterAssetDialog {
canvasEl.style.maxHeight = "100%";
previewWrap.appendChild(canvasEl);
const meta = document.createElement("div");
meta.className = "alekpet_painter_storage_item_meta";
const id = document.createElement("div");
id.textContent = asset.asset_id;
id.title = asset.asset_id;
id.className = "alekpet_painter_storage_item_id_text";
id.className = "alekpet_painter_storage_item_id";
const meta = document.createElement("div");
meta.textContent = `${formatBytes(asset.size)}${asset.width && asset.height ? ` · ${asset.width}×${asset.height}` : ""}`;
meta.style.fontSize = "11px";
const size = document.createElement("div");
size.title = "Data size · Pixel resolution";
size.innerHTML = `Size: <span>${formatBytes(asset.size)}</span> · WxH: <span>${asset.width && asset.height ? ` · ${asset.width}×${asset.height}` : ""}</span>`;
size.className =
"alekpet_painter_storage_item_skew_box alekpet_painter_storage_item_size";
const workflow_name = document.createElement("div");
workflow_name.innerHTML = `Workflow: <span>${asset.workflow_name}</span>`;
workflow_name.title = `Last used workflow: ${asset.workflow_name}`;
workflow_name.className =
"alekpet_painter_storage_item_skew_box alekpet_painter_storage_item_workflow_name";
const buttons = document.createElement("div");
Object.assign(buttons.style, { display: "flex", gap: "6px" });
const add = document.createElement("button");
add.textContent = "➕ Add to scene";
add.onclick = () => this.addToScene(asset.asset_id);
add.textContent = "➕ Add as copy";
add.title = "Add to workflow as copy.";
add.onclick = () => this.addToWorkflow(asset.asset_id);
const addRef = document.createElement("button");
addRef.textContent = "🔗 Add as reference";
addRef.title =
"Add to workflow as reference. Attention: the change will affect all copies in workflows!";
addRef.onclick = () => this.addToWorkflow(asset.asset_id, true);
const remove = document.createElement("button");
remove.textContent = "🗑 Delete";
remove.textContent = "🗑️";
remove.title = "Remove canvas data";
remove.className = "alekpet_painter_storage_button_close";
remove.onclick = () => this.deleteAsset(asset.asset_id, card);
buttons.append(add, remove);
buttons.append(add, addRef, remove);
meta.append(id, workflow_name, size);
card.append(previewWrap, id, meta, buttons);
this.items.appendChild(card);
@@ -182,7 +203,7 @@ export class PainterAssetDialog {
});
}
async addToScene(assetId) {
async addToWorkflow(assetId, persist = false) {
const LiteGraph = globalThis.LiteGraph;
if (!LiteGraph) {
console.error("[PainterAssetDialog] LiteGraph is unavailable");
@@ -219,10 +240,13 @@ export class PainterAssetDialog {
await painter.loadCanvasData(assetRef(assetId), painterIndex, {
migrateImage: false,
persist,
});
painter.node.painterAsset.asset_id = createPainterAssetId();
await painter.persistPainterState();
if (!persist) {
painter.node.painterAsset.asset_id = createPainterAssetId();
await painter.persistPainterState();
}
app.graph.setDirtyCanvas(true, false);
}
+20 -4
View File
@@ -118,6 +118,16 @@ async function getLoadedFonts() {
});
});
}
function getActiveWorkflowName() {
const workflow = app?.extensionManager?.workflow?.activeWorkflow;
return (
workflow?.filename?.replace(/\.json$/i, "") ||
workflow?.key ||
"Unsaved Workflow"
);
}
// ================= END FUNCTIONS ================
// ================= CLASS PAINTER ================
@@ -2501,7 +2511,10 @@ class Painter {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ state: nextState }),
body: JSON.stringify({
state: nextState,
workflow_name: getActiveWorkflowName(),
}),
}
);
@@ -2874,7 +2887,7 @@ class Painter {
// - end
}
async loadCanvasData(data, painter_idx, options = {}) {
async loadCanvasData(data, painter_idx, options = { persist: true }) {
// New workflows store only {version, asset_id}
if (isPainterAssetRef(data)) {
this.node.painterAsset = {
@@ -2939,7 +2952,10 @@ class Painter {
}
// Migrate legacy workflow data, or repair a missing backend asset
await this.persistPainterState(data);
if (options.persist !== false) {
await this.persistPainterState(data);
}
if (data?.canvas_settings && options.migrateImage !== false) {
await this.uploadPaintFile(this.node.name);
}
@@ -3237,7 +3253,7 @@ app.registerExtension({
// Managing data
app.ui.settings.addSetting({
id: `${extensionName}.ManagingData`,
name: "🔸 Managing JSON data storage",
name: "🔸 Managing JSON data assets",
defaultValue: false,
type: (name, sett, val) => {
return makeElement("tr", {