feat(workflows): add right-click update from selection or canvas (#133)

* feat(workflows): add context-menu update from current canvas

* feat(workflows): update from selection or canvas

* refactor(workflows): keep sidebar app access inside canvas_io

* fix(workflows): repair selection-update block syntax and duplication

* nit(workflows): clarify selection-unavailable toast path

---------

Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com>
This commit is contained in:
k_flame_99
2026-05-14 19:39:42 +02:00
committed by GitHub
co-authored by kflame99
parent e87d111521
commit 41ff4de460
2 changed files with 77 additions and 0 deletions
+12
View File
@@ -81,6 +81,18 @@ function getSelectedNodeIds() {
}
}
export function getSelectedNodeCount() {
return getSelectedNodeIds().size;
}
export function getCanvasNodeCount() {
try {
return (app.graph && app.graph._nodes && app.graph._nodes.length) || 0;
} catch (e) {
return 0;
}
}
function idKey(id) {
return id == null ? null : String(id);
}
+65
View File
@@ -68,6 +68,8 @@ import {
insertWorkflowOntoCanvas,
insertNode,
getSelectedNodeTypes,
getSelectedNodeCount,
getCanvasNodeCount,
dropPlaceholdersForPacks,
} from "./canvas_io.js";
import { discoverMissingPacks } from "./installer.js";
@@ -1611,6 +1613,68 @@ function workflowRowContextMenu(event, dirPath, wfName, isArchived = false) {
},
};
const updateFromCanvasItem = {
label: "Update from selection or canvas",
action: () => {
if (!canvasIsNonEmpty()) {
toast("Canvas is empty.");
return;
}
const selectedCount = getSelectedNodeCount();
const totalNodeCount = getCanvasNodeCount();
const allNodesSelected = selectedCount > 0 && selectedCount === totalNodeCount;
let graph = null;
let sourceLabel = "canvas";
if (selectedCount > 0 && !allNodesSelected) {
const selectionResult = serializeSelection();
if (selectionResult.kind === "stale") {
toast("Selected node(s) no longer exist.");
return;
}
if (selectionResult.kind !== "ok") {
toast("Selection unavailable. Re-select nodes and try again.");
return;
}
if (!selectionResult.graph || !Array.isArray(selectionResult.graph.nodes) || selectionResult.graph.nodes.length === 0) {
toast("Failed to serialize selection. See console.");
return;
}
graph = selectionResult.graph;
sourceLabel = "selection";
} else {
graph = serializeFullCanvas();
if (!graph || !Array.isArray(graph.nodes) || graph.nodes.length === 0) {
toast("Failed to serialize canvas. See console.");
return;
}
}
const sourceTags = getWorkflowTags(dirPath, wfName) || [];
const sourceIsModule = isWorkflowModule(dirPath, wfName);
persistMutation({
mutate: () => {
if (getWorkflowGraph(dirPath, wfName) === null) return false;
const result = saveWorkflowEntry(dirPath, wfName, graph, { module: sourceIsModule });
if (!result) return false;
for (const t of sourceTags) addTag(dirPath, wfName, t);
if (sourceIsModule) addTag(dirPath, wfName, MODULE_TAG);
return result;
},
onSuccess: (result) => {
if (result && result.archivedAs) {
toast(`Updated "${wfName}" from ${sourceLabel} (previous version archived as "${result.archivedAs}").`);
} else {
toast(`Updated "${wfName}" from ${sourceLabel}.`);
}
},
onNoOp: () => toast(`Could not update — "${wfName}" no longer exists.`),
});
},
};
const tagsItem = {
label: "Tags…",
action: () => {
@@ -1649,6 +1713,7 @@ function workflowRowContextMenu(event, dirPath, wfName, isArchived = false) {
label: "Insert into canvas",
action: () => insertWorkflowOntoCanvas(dirPath, wfName),
},
...(!isArchived ? [updateFromCanvasItem] : []),
{
label: "Rename…",
action: () => {