feat: ProjectSource auto-fills active project from Manager
Added /api/active-project endpoint that reads current_project from config. ProjectSource now hides the project_name widget and fetches the active project automatically on create, load, and click. Title shows "Source: <label> [<project>]" for at-a-glance status. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+7
-1
@@ -13,7 +13,7 @@ from fastapi.responses import FileResponse
|
|||||||
from nicegui import app
|
from nicegui import app
|
||||||
|
|
||||||
from db import ProjectDB
|
from db import ProjectDB
|
||||||
from utils import load_json, KEY_BATCH_DATA, KEY_SEQUENCE_NUMBER
|
from utils import load_json, load_config, KEY_BATCH_DATA, KEY_SEQUENCE_NUMBER
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ def register_api_routes(db: ProjectDB) -> None:
|
|||||||
_db = db
|
_db = db
|
||||||
|
|
||||||
app.add_api_route("/api/projects", _list_projects, methods=["GET"])
|
app.add_api_route("/api/projects", _list_projects, methods=["GET"])
|
||||||
|
app.add_api_route("/api/active-project", _get_active_project, methods=["GET"])
|
||||||
app.add_api_route("/api/projects/{name}/files", _list_files, methods=["GET"])
|
app.add_api_route("/api/projects/{name}/files", _list_files, methods=["GET"])
|
||||||
app.add_api_route("/api/projects/{name}/files/{file_name}/sequences", _list_sequences, methods=["GET"])
|
app.add_api_route("/api/projects/{name}/files/{file_name}/sequences", _list_sequences, methods=["GET"])
|
||||||
app.add_api_route("/api/projects/{name}/files/{file_name}/data", _get_data, methods=["GET"])
|
app.add_api_route("/api/projects/{name}/files/{file_name}/data", _get_data, methods=["GET"])
|
||||||
@@ -46,6 +47,11 @@ def _list_projects() -> dict[str, Any]:
|
|||||||
return {"projects": [p["name"] for p in projects]}
|
return {"projects": [p["name"] for p in projects]}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_active_project() -> dict[str, Any]:
|
||||||
|
config = load_config()
|
||||||
|
return {"project": config.get("current_project", "")}
|
||||||
|
|
||||||
|
|
||||||
def _list_files(name: str) -> dict[str, Any]:
|
def _list_files(name: str) -> dict[str, Any]:
|
||||||
db = _get_db()
|
db = _get_db()
|
||||||
files = db.list_project_files(name)
|
files = db.list_project_files(name)
|
||||||
|
|||||||
+69
-27
@@ -28,6 +28,35 @@ app.registerExtension({
|
|||||||
return combo;
|
return combo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch active project from Manager and update project_name + title
|
||||||
|
async function refreshActiveProject(node) {
|
||||||
|
const urlW = node.widgets?.find(w => w.name === "manager_url");
|
||||||
|
if (!urlW?.value) return;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${urlW.value}/api/active-project`);
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const data = await resp.json();
|
||||||
|
const project = data.project || "";
|
||||||
|
const projW = node.widgets?.find(w => w.name === "project_name");
|
||||||
|
if (projW && projW.value !== project) {
|
||||||
|
projW.value = project;
|
||||||
|
await refreshFiles(node);
|
||||||
|
}
|
||||||
|
_updateTitle(node);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[ProjectSource] Failed to fetch active project:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateTitle(node) {
|
||||||
|
const labelW = node.widgets?.find(w => w.name === "label");
|
||||||
|
const projW = node.widgets?.find(w => w.name === "project_name");
|
||||||
|
const label = labelW?.value || "";
|
||||||
|
const project = projW?.value || "?";
|
||||||
|
node.title = label ? `Source: ${label} [${project}]` : `Project Source [${project}]`;
|
||||||
|
app.graph?.setDirtyCanvas(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch file list from API and update file_name combo
|
// Fetch file list from API and update file_name combo
|
||||||
async function refreshFiles(node) {
|
async function refreshFiles(node) {
|
||||||
const urlW = node.widgets?.find(w => w.name === "manager_url");
|
const urlW = node.widgets?.find(w => w.name === "manager_url");
|
||||||
@@ -84,22 +113,28 @@ app.registerExtension({
|
|||||||
|
|
||||||
const node = this;
|
const node = this;
|
||||||
|
|
||||||
|
// Hide project_name — it is auto-filled from the Manager's active project
|
||||||
|
const projW = this.widgets?.find(w => w.name === "project_name");
|
||||||
|
if (projW) {
|
||||||
|
if (projW.origType === undefined) projW.origType = projW.type;
|
||||||
|
projW.type = "hidden";
|
||||||
|
projW.hidden = true;
|
||||||
|
projW.computeSize = () => [0, -4];
|
||||||
|
}
|
||||||
|
|
||||||
// Replace file_name STRING with a combo
|
// Replace file_name STRING with a combo
|
||||||
replaceWithCombo(this, "file_name", [], function (value) {
|
replaceWithCombo(this, "file_name", [], function (value) {
|
||||||
notifyRelays(node);
|
notifyRelays(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hook manager_url and project_name to refresh file list + notify relays
|
// Hook manager_url to refresh active project + files + notify relays
|
||||||
for (const name of ["manager_url", "project_name"]) {
|
const urlW = this.widgets?.find(w => w.name === "manager_url");
|
||||||
const w = this.widgets?.find(w => w.name === name);
|
if (urlW) {
|
||||||
if (w) {
|
const origCb = urlW.callback;
|
||||||
const origCb = w.callback;
|
urlW.callback = function (...args) {
|
||||||
w.callback = function (...args) {
|
origCb?.apply(this, args);
|
||||||
origCb?.apply(this, args);
|
refreshActiveProject(node).then(() => notifyRelays(node));
|
||||||
refreshFiles(node);
|
};
|
||||||
notifyRelays(node);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook sequence_number to notify relays
|
// Hook sequence_number to notify relays
|
||||||
@@ -118,22 +153,27 @@ app.registerExtension({
|
|||||||
const origCallback = labelWidget.callback;
|
const origCallback = labelWidget.callback;
|
||||||
labelWidget.callback = function (...args) {
|
labelWidget.callback = function (...args) {
|
||||||
origCallback?.apply(this, args);
|
origCallback?.apply(this, args);
|
||||||
node.title = labelWidget.value
|
_updateTitle(node);
|
||||||
? `Source: ${labelWidget.value}`
|
|
||||||
: "Project Source";
|
|
||||||
app.graph?.setDirtyCanvas(true, true);
|
|
||||||
};
|
};
|
||||||
// Set initial title
|
|
||||||
if (labelWidget.value) {
|
|
||||||
this.title = `Source: ${labelWidget.value}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-fetch active project on creation
|
||||||
|
queueMicrotask(() => refreshActiveProject(node));
|
||||||
};
|
};
|
||||||
|
|
||||||
const origOnConfigure = nodeType.prototype.onConfigure;
|
const origOnConfigure = nodeType.prototype.onConfigure;
|
||||||
nodeType.prototype.onConfigure = function (info) {
|
nodeType.prototype.onConfigure = function (info) {
|
||||||
origOnConfigure?.apply(this, arguments);
|
origOnConfigure?.apply(this, arguments);
|
||||||
|
|
||||||
|
// Hide project_name (may have been serialized as visible)
|
||||||
|
const projW = this.widgets?.find(w => w.name === "project_name");
|
||||||
|
if (projW) {
|
||||||
|
if (projW.origType === undefined) projW.origType = projW.type;
|
||||||
|
projW.type = "hidden";
|
||||||
|
projW.hidden = true;
|
||||||
|
projW.computeSize = () => [0, -4];
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure file_name is a combo (may be STRING from serialization)
|
// Ensure file_name is a combo (may be STRING from serialization)
|
||||||
const fileW = this.widgets?.find(w => w.name === "file_name");
|
const fileW = this.widgets?.find(w => w.name === "file_name");
|
||||||
if (fileW && fileW.type !== "combo") {
|
if (fileW && fileW.type !== "combo") {
|
||||||
@@ -143,16 +183,18 @@ app.registerExtension({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelWidget = this.widgets?.find(w => w.name === "label");
|
_updateTitle(this);
|
||||||
if (labelWidget?.value) {
|
|
||||||
this.title = `Source: ${labelWidget.value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deferred: refresh file list once graph is ready
|
// Deferred: fetch active project (and files) once graph is ready
|
||||||
const node = this;
|
const node = this;
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => refreshActiveProject(node));
|
||||||
refreshFiles(node);
|
};
|
||||||
});
|
|
||||||
|
// Re-check active project on click (picks up changes made in the Manager)
|
||||||
|
const origOnMouseDown = nodeType.prototype.onMouseDown;
|
||||||
|
nodeType.prototype.onMouseDown = function (e, localPos, graphCanvas) {
|
||||||
|
origOnMouseDown?.apply(this, arguments);
|
||||||
|
refreshActiveProject(this);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user