diff --git a/PainterNode/painter_asset_storage.py b/PainterNode/painter_asset_storage.py
index e981bce..58deb3a 100644
--- a/PainterNode/painter_asset_storage.py
+++ b/PainterNode/painter_asset_storage.py
@@ -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-",
diff --git a/web_alekpet_nodes/css/painternode/painter_node_styles.css b/web_alekpet_nodes/css/painternode/painter_node_styles.css
index 8883864..d55a2d2 100644
--- a/web_alekpet_nodes/css/painternode/painter_node_styles.css
+++ b/web_alekpet_nodes/css/painternode/painter_node_styles.css
@@ -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 */
diff --git a/web_alekpet_nodes/lib/painternode/painter_asset_dialog.js b/web_alekpet_nodes/lib/painternode/painter_asset_dialog.js
index 6d14437..9dff372 100644
--- a/web_alekpet_nodes/lib/painternode/painter_asset_dialog.js
+++ b/web_alekpet_nodes/lib/painternode/painter_asset_dialog.js
@@ -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: ${formatBytes(asset.size)} · WxH: ${asset.width && asset.height ? ` · ${asset.width}×${asset.height}` : ""}`;
+ size.className =
+ "alekpet_painter_storage_item_skew_box alekpet_painter_storage_item_size";
+
+ const workflow_name = document.createElement("div");
+ workflow_name.innerHTML = `Workflow: ${asset.workflow_name}`;
+ 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);
}
diff --git a/web_alekpet_nodes/painter_node.js b/web_alekpet_nodes/painter_node.js
index 953e0e5..b394a50 100644
--- a/web_alekpet_nodes/painter_node.js
+++ b/web_alekpet_nodes/painter_node.js
@@ -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", {