diff --git a/topology_api/queries/snapshot_queries.py b/topology_api/queries/snapshot_queries.py index a6c6604..f8c2207 100644 --- a/topology_api/queries/snapshot_queries.py +++ b/topology_api/queries/snapshot_queries.py @@ -3,6 +3,11 @@ 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): """Return missions for a base with their latest recorded event.""" with read_cursor() as cur: @@ -249,6 +254,10 @@ def fetch_layout(): dl.name AS layout_name, dl.canvas_width, 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.component_id, c.component_key, @@ -269,12 +278,18 @@ def fetch_layout(): ON member_component.id = member.component_id WHERE member.diagram_node_id = dn.id ) 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') AND dl.is_default = true ORDER BY dn.z_index, dn.node_key """) nodes = [] layout_info = None + theme_info = None for row in cur.fetchall(): if layout_info is None: layout_info = { @@ -282,6 +297,13 @@ def fetch_layout(): "name": row["layout_name"], "canvasWidth": float(row["canvas_width"]), "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({ "nodeKey": row["node_key"], @@ -298,4 +320,111 @@ def fetch_layout(): "decorations": row["decorations"] 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, + }, + }