feat(topology): return diagram presentation styles

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-09 23:46:13 +08:00
co-authored by Claude
parent d214d93f71
commit 7deda3a589
+130 -1
View File
@@ -3,6 +3,11 @@
from topology_api.db import read_cursor from topology_api.db import read_cursor
def _require_presentation(condition, message):
if not condition:
raise RuntimeError(f"presentation_config_error: {message}")
def fetch_missions(base_code): def fetch_missions(base_code):
"""Return missions for a base with their latest recorded event.""" """Return missions for a base with their latest recorded event."""
with read_cursor() as cur: with read_cursor() as cur:
@@ -249,6 +254,10 @@ def fetch_layout():
dl.name AS layout_name, dl.name AS layout_name,
dl.canvas_width, dl.canvas_width,
dl.canvas_height, dl.canvas_height,
COALESCE(layout_theme.id, default_theme.id) AS theme_id,
COALESCE(layout_theme.theme_key, default_theme.theme_key) AS theme_key,
COALESCE(layout_theme.name, default_theme.name) AS theme_name,
COALESCE(layout_theme.tokens, default_theme.tokens) AS theme_tokens,
dn.node_key, dn.node_key,
dn.component_id, dn.component_id,
c.component_key, c.component_key,
@@ -269,12 +278,18 @@ def fetch_layout():
ON member_component.id = member.component_id ON member_component.id = member.component_id
WHERE member.diagram_node_id = dn.id WHERE member.diagram_node_id = dn.id
) members ON true ) members ON true
LEFT JOIN diagram_themes layout_theme
ON layout_theme.id = dl.theme_id
LEFT JOIN diagram_themes default_theme
ON default_theme.base_id = dl.base_id
AND default_theme.is_default = true
WHERE dl.base_id = (SELECT id FROM bases WHERE code = 'guanghan') WHERE dl.base_id = (SELECT id FROM bases WHERE code = 'guanghan')
AND dl.is_default = true AND dl.is_default = true
ORDER BY dn.z_index, dn.node_key ORDER BY dn.z_index, dn.node_key
""") """)
nodes = [] nodes = []
layout_info = None layout_info = None
theme_info = None
for row in cur.fetchall(): for row in cur.fetchall():
if layout_info is None: if layout_info is None:
layout_info = { layout_info = {
@@ -282,6 +297,13 @@ def fetch_layout():
"name": row["layout_name"], "name": row["layout_name"],
"canvasWidth": float(row["canvas_width"]), "canvasWidth": float(row["canvas_width"]),
"canvasHeight": float(row["canvas_height"]), "canvasHeight": float(row["canvas_height"]),
"themeKey": row["theme_key"],
}
theme_info = {
"id": row["theme_id"],
"key": row["theme_key"],
"name": row["theme_name"],
"tokens": row["theme_tokens"] or {},
} }
nodes.append({ nodes.append({
"nodeKey": row["node_key"], "nodeKey": row["node_key"],
@@ -298,4 +320,111 @@ def fetch_layout():
"decorations": row["decorations"] or {}, "decorations": row["decorations"] or {},
"memberKeys": row["member_keys"] or [], "memberKeys": row["member_keys"] or [],
}) })
return {"layout": layout_info, "nodes": nodes} if layout_info else None if layout_info is None:
return None
_require_presentation(theme_info and theme_info["id"], "default theme missing")
theme_id_val = theme_info["id"]
cur.execute("""
SELECT style_key, display_name, fill_color, stroke_color,
stroke_width, corner_radius, title_color, meta_color,
hint_color
FROM diagram_node_styles
WHERE theme_id = %(theme)s
ORDER BY style_key
""", {"theme": theme_id_val})
node_styles = {
row["style_key"]: {
"displayName": row["display_name"],
"fillColor": row["fill_color"],
"strokeColor": row["stroke_color"],
"strokeWidth": float(row["stroke_width"]),
"cornerRadius": float(row["corner_radius"]),
"titleColor": row["title_color"],
"metaColor": row["meta_color"],
"hintColor": row["hint_color"],
}
for row in cur.fetchall()
}
cur.execute("""
SELECT connection_type, display_name, stroke_color, stroke_width,
dash_array, line_cap, legend_group
FROM diagram_edge_styles
WHERE theme_id = %(theme)s
ORDER BY connection_type
""", {"theme": theme_id_val})
edge_styles = {
row["connection_type"]: {
"displayName": row["display_name"],
"strokeColor": row["stroke_color"],
"strokeWidth": float(row["stroke_width"]),
"dashArray": row["dash_array"],
"lineCap": row["line_cap"],
"legendGroup": row["legend_group"],
}
for row in cur.fetchall()
}
cur.execute("""
SELECT decoration_key, display_name, fill_color, stroke_color,
stroke_width, label, label_color, render_params
FROM diagram_decoration_styles
WHERE theme_id = %(theme)s
ORDER BY decoration_key
""", {"theme": theme_id_val})
decoration_styles = {
row["decoration_key"]: {
"displayName": row["display_name"],
"fillColor": row["fill_color"],
"strokeColor": row["stroke_color"],
"strokeWidth": float(row["stroke_width"]),
"label": row["label"],
"labelColor": row["label_color"],
"renderParams": row["render_params"] or {},
}
for row in cur.fetchall()
}
cur.execute("""
SELECT legend_key, label, item_type, style_ref, display_order
FROM diagram_legend_items
WHERE theme_id = %(theme)s
AND is_visible = true
ORDER BY display_order, legend_key
""", {"theme": theme_id_val})
legend_items = [
{
"key": row["legend_key"],
"label": row["label"],
"itemType": row["item_type"],
"styleRef": row["style_ref"],
"displayOrder": row["display_order"],
}
for row in cur.fetchall()
]
missing_node_styles = {
node["styleKey"] for node in nodes
} - set(node_styles)
_require_presentation(
not missing_node_styles,
f"missing node styles: {sorted(missing_node_styles)}",
)
return {
"layout": layout_info,
"nodes": nodes,
"presentation": {
"theme": {
"key": theme_info["key"],
"name": theme_info["name"],
"tokens": theme_info["tokens"],
},
"nodeStyles": node_styles,
"edgeStyles": edge_styles,
"decorationStyles": decoration_styles,
"legendItems": legend_items,
},
}