Add HTML5 flowchart depth controls
This commit is contained in:
@@ -252,11 +252,11 @@ def render_html5_flowchart(
|
|||||||
flowchart: object | None,
|
flowchart: object | None,
|
||||||
*,
|
*,
|
||||||
focus: str | None = None,
|
focus: str | None = None,
|
||||||
|
depth: int = 1,
|
||||||
oob: bool = False,
|
oob: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
hx_url = f"/html5/projects/{quote(project_id)}/flowchart"
|
normalized_depth = min(max(depth, 1), 3)
|
||||||
if focus:
|
hx_url = _flowchart_url(project_id, focus, normalized_depth)
|
||||||
hx_url = f"{hx_url}?focus={quote(focus, safe='')}"
|
|
||||||
oob_attr = ' hx-swap-oob="outerHTML"' if oob else ""
|
oob_attr = ' hx-swap-oob="outerHTML"' if oob else ""
|
||||||
if flowchart is None:
|
if flowchart is None:
|
||||||
return f"""
|
return f"""
|
||||||
@@ -290,6 +290,7 @@ def render_html5_flowchart(
|
|||||||
{oob_attr}
|
{oob_attr}
|
||||||
>
|
>
|
||||||
<div class="panel-title">Карта связей · {escape(mode)}</div>
|
<div class="panel-title">Карта связей · {escape(mode)}</div>
|
||||||
|
{_flowchart_depth_actions(project_id, focus, normalized_depth)}
|
||||||
<dl class="report-grid">
|
<dl class="report-grid">
|
||||||
{_metric("Nodes", len(nodes))}
|
{_metric("Nodes", len(nodes))}
|
||||||
{_metric("Edges", len(edges))}
|
{_metric("Edges", len(edges))}
|
||||||
@@ -1750,6 +1751,34 @@ def _flowchart_edge_item(edge: object, nodes: Iterable[object]) -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _flowchart_url(project_id: str, focus: str | None, depth: int) -> str:
|
||||||
|
params = []
|
||||||
|
if focus:
|
||||||
|
params.append(f"focus={quote(focus, safe='')}")
|
||||||
|
params.append(f"depth={depth}")
|
||||||
|
return f"/html5/projects/{quote(project_id)}/flowchart?{'&'.join(params)}"
|
||||||
|
|
||||||
|
|
||||||
|
def _flowchart_depth_actions(project_id: str, focus: str | None, active_depth: int) -> str:
|
||||||
|
buttons = []
|
||||||
|
for depth in [1, 2, 3]:
|
||||||
|
active = ' aria-current="page" data-html5-object-action-active="true"' if depth == active_depth else ""
|
||||||
|
url = _flowchart_url(project_id, focus, depth)
|
||||||
|
buttons.append(
|
||||||
|
f"""
|
||||||
|
<a
|
||||||
|
class="button"
|
||||||
|
href="{url}"
|
||||||
|
hx-get="{url}"
|
||||||
|
hx-target="[data-html5-flowchart]"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
{active}
|
||||||
|
>Depth {depth}</a>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
return f'<nav class="object-actions" data-html5-flowchart-actions>{"".join(buttons)}</nav>'
|
||||||
|
|
||||||
|
|
||||||
def _flowchart_node_item(node: object) -> str:
|
def _flowchart_node_item(node: object) -> str:
|
||||||
name = str(getattr(node, "qualified_name", "") or getattr(node, "label", "") or "node")
|
name = str(getattr(node, "qualified_name", "") or getattr(node, "label", "") or "node")
|
||||||
kind = str(getattr(node, "kind", "") or "NODE")
|
kind = str(getattr(node, "kind", "") or "NODE")
|
||||||
|
|||||||
@@ -1752,7 +1752,7 @@ async def html5_project_flowchart(
|
|||||||
) -> Response:
|
) -> Response:
|
||||||
flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit)
|
flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit)
|
||||||
return Response(
|
return Response(
|
||||||
render_html5_flowchart(project_id, flowchart, focus=focus),
|
render_html5_flowchart(project_id, flowchart, focus=focus, depth=depth),
|
||||||
media_type="text/html; charset=utf-8",
|
media_type="text/html; charset=utf-8",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1784,7 +1784,7 @@ async def html5_project_object_context(project_id: str, object_name: str, mode:
|
|||||||
flowchart,
|
flowchart,
|
||||||
mode,
|
mode,
|
||||||
)
|
)
|
||||||
flowchart_context = render_html5_flowchart(project_id, flowchart, focus=object_name, oob=True)
|
flowchart_context = render_html5_flowchart(project_id, flowchart, focus=object_name, depth=1, oob=True)
|
||||||
source_context = render_html5_source(source_node, oob=True) if source_node is not None else ""
|
source_context = render_html5_source(source_node, oob=True) if source_node is not None else ""
|
||||||
symbol_context = render_html5_symbol_detail(project_id, symbol_references, oob=True)
|
symbol_context = render_html5_symbol_detail(project_id, symbol_references, oob=True)
|
||||||
report_context = render_html5_object_report(
|
report_context = render_html5_object_report(
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def test_html5_server_rendered_project_editor(tmp_path: Path):
|
|||||||
assert "data-html5-symbol-results" in editor.text
|
assert "data-html5-symbol-results" in editor.text
|
||||||
assert "data-html5-symbol-detail" in editor.text
|
assert "data-html5-symbol-detail" in editor.text
|
||||||
assert "data-html5-flowchart" in editor.text
|
assert "data-html5-flowchart" in editor.text
|
||||||
assert f'hx-get="/html5/projects/{project_id}/flowchart"' in editor.text
|
assert f'hx-get="/html5/projects/{project_id}/flowchart?depth=1"' in editor.text
|
||||||
assert "data-html5-project-report" in editor.text
|
assert "data-html5-project-report" in editor.text
|
||||||
assert f'hx-get="/html5/projects/{project_id}/report"' in editor.text
|
assert f'hx-get="/html5/projects/{project_id}/report"' in editor.text
|
||||||
assert "data-html5-review" in editor.text
|
assert "data-html5-review" in editor.text
|
||||||
@@ -421,6 +421,12 @@ def test_html5_flowchart_fragment(tmp_path: Path):
|
|||||||
assert flowchart.status_code == 200
|
assert flowchart.status_code == 200
|
||||||
assert "text/html" in flowchart.headers["content-type"]
|
assert "text/html" in flowchart.headers["content-type"]
|
||||||
assert "data-html5-flowchart" in flowchart.text
|
assert "data-html5-flowchart" in flowchart.text
|
||||||
|
assert "data-html5-flowchart-actions" in flowchart.text
|
||||||
|
assert "Depth 1" in flowchart.text
|
||||||
|
assert "Depth 2" in flowchart.text
|
||||||
|
assert "Depth 3" in flowchart.text
|
||||||
|
assert "depth=2" in flowchart.text
|
||||||
|
assert 'hx-target="[data-html5-flowchart]"' in flowchart.text
|
||||||
assert 'hx-swap-oob="outerHTML"' not in flowchart.text
|
assert 'hx-swap-oob="outerHTML"' not in flowchart.text
|
||||||
assert "Карта связей" in flowchart.text
|
assert "Карта связей" in flowchart.text
|
||||||
assert "Nodes" in flowchart.text
|
assert "Nodes" in flowchart.text
|
||||||
@@ -428,6 +434,14 @@ def test_html5_flowchart_fragment(tmp_path: Path):
|
|||||||
assert "ПровестиЗаказ" in flowchart.text or "ПроверитьОстатки" in flowchart.text
|
assert "ПровестиЗаказ" in flowchart.text or "ПроверитьОстатки" in flowchart.text
|
||||||
assert "<html" not in flowchart.text
|
assert "<html" not in flowchart.text
|
||||||
|
|
||||||
|
deep_flowchart = client.get(
|
||||||
|
f"/html5/projects/{project_id}/flowchart",
|
||||||
|
params={"focus": "ПровестиЗаказ", "depth": 2},
|
||||||
|
)
|
||||||
|
assert deep_flowchart.status_code == 200
|
||||||
|
assert "depth=2" in deep_flowchart.text
|
||||||
|
assert 'data-html5-object-action-active="true"' in deep_flowchart.text
|
||||||
|
|
||||||
|
|
||||||
def test_html5_project_setup_renders_server_fragments():
|
def test_html5_project_setup_renders_server_fragments():
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|||||||
Reference in New Issue
Block a user