Fix graph flow ancestry and modal sizing
This commit is contained in:
parent
c22ae01fdf
commit
984a4fd993
5 changed files with 91 additions and 43 deletions
|
|
@ -33,7 +33,7 @@ from .errors import DocForgeError
|
|||
from .index import APPLICATION_ID, INDEX_SCHEMA_VERSION, ProjectIndex, re_tokenize
|
||||
from .project import project_root_fingerprint
|
||||
|
||||
VISUALIZATION_TEMPLATE = "graph-browser@8"
|
||||
VISUALIZATION_TEMPLATE = "graph-browser@10"
|
||||
DEFAULT_EDGE_LIMIT = 100
|
||||
MAX_EDGE_LIMIT = 400
|
||||
DEFAULT_INITIAL_GRACE_SECONDS = 120.0
|
||||
|
|
@ -1447,23 +1447,25 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
background: #07111c; color: #d6e6f5;
|
||||
}
|
||||
dialog {
|
||||
width: min(760px, calc(100vw - 32px)); height: min(680px, calc(100vh - 32px));
|
||||
min-width: min(360px, calc(100vw - 20px)); min-height: 280px;
|
||||
max-width: calc(100vw - 16px); max-height: calc(100vh - 16px);
|
||||
width: fit-content; height: fit-content;
|
||||
min-width: min(360px, calc(100vw - 20px)); min-height: 0;
|
||||
max-width: min(760px, calc(100vw - 32px)); max-height: calc(100vh - 32px);
|
||||
padding: 0; overflow: hidden; border: 1px solid #36536e; border-radius: 14px;
|
||||
background: var(--panel); color: var(--text);
|
||||
box-shadow: 0 24px 80px rgba(0, 0, 0, .58); resize: both;
|
||||
}
|
||||
dialog::backdrop { background: rgba(2, 8, 14, .48); }
|
||||
.dialog-shell {
|
||||
display: grid; grid-template-rows: auto minmax(0, 1fr) auto; width: 100%; height: 100%;
|
||||
display: grid; grid-template-rows: auto auto auto; width: fit-content;
|
||||
min-width: min(360px, calc(100vw - 20px)); max-width: min(760px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
}
|
||||
.dialog-head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--panel-2);
|
||||
cursor: move; touch-action: none; user-select: none;
|
||||
}
|
||||
.dialog-head strong { font-size: 15px; }
|
||||
.dialog-head strong { min-width: 0; font-size: 15px; overflow-wrap: anywhere; }
|
||||
.dialog-close {
|
||||
width: 34px; height: 34px; border: 1px solid var(--line); border-radius: 8px;
|
||||
background: #102b3d; color: var(--text); font-size: 21px; line-height: 1;
|
||||
|
|
@ -1474,16 +1476,22 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
.dialog-body { min-height: 0; overflow: auto; padding: 18px; }
|
||||
.dialog-body pre { max-height: none; }
|
||||
dialog.compact-dialog {
|
||||
width: min(520px, calc(100vw - 32px)); height: min(620px, calc(100vh - 32px));
|
||||
resize: none;
|
||||
position: fixed; inset: auto; margin: 0; width: min(400px, calc(100vw - 24px));
|
||||
height: auto; min-width: 0; min-height: 0; max-height: min(560px, calc(100vh - 24px));
|
||||
resize: none; border-color: rgba(91, 137, 169, .78); background: rgba(7, 18, 29, .82);
|
||||
box-shadow: 0 14px 42px rgba(0, 0, 0, .42);
|
||||
}
|
||||
.compact-dialog .dialog-head { cursor: default; }
|
||||
.compact-dialog .dialog-body { padding: 16px; }
|
||||
.compact-dialog .dialog-body pre { max-height: 230px; }
|
||||
.compact-dialog .dialog-shell { grid-template-rows: auto auto; }
|
||||
.compact-dialog .dialog-body { padding: 14px 16px; }
|
||||
.compact-dialog .dialog-body pre { display: none; }
|
||||
.compact-dialog .detail-head h2 { font-size: 16px; }
|
||||
.compact-dialog .meta { margin: 12px 0 0; gap: 6px; }
|
||||
.compact-dialog .meta div { grid-template-columns: 66px 1fr; }
|
||||
.dialog-actions {
|
||||
display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px;
|
||||
border-top: 1px solid var(--line); background: var(--panel-2);
|
||||
}
|
||||
.compact-dialog .dialog-actions { padding: 9px 12px; background: rgba(12, 35, 51, .72); }
|
||||
.error { color: #ff9aac; }
|
||||
@media (max-width: 980px) {
|
||||
:root { --left-width: 240px; --right-width: 260px; }
|
||||
|
|
@ -1597,17 +1605,11 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="compact-dialog" id="node-card" aria-labelledby="node-card-label">
|
||||
<dialog class="compact-dialog" id="node-card" aria-label="Selected node descriptor">
|
||||
<div class="dialog-shell">
|
||||
<div class="dialog-head">
|
||||
<strong id="node-card-label">Node descriptor</strong>
|
||||
<button class="dialog-close" id="close-node-card" type="button"
|
||||
aria-label="Close node descriptor">×</button>
|
||||
</div>
|
||||
<div class="dialog-body" id="node-card-details"></div>
|
||||
<div class="dialog-actions">
|
||||
<button class="button" id="explore-card-node" type="button">Explore neighborhood</button>
|
||||
<button class="button" id="dismiss-node-card" type="button">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
@ -1635,7 +1637,7 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
const relationStyles = Object.freeze({
|
||||
contains: {
|
||||
family: "Structure", color: "#60a5fa", dash: "", marker: "diamond-arrow",
|
||||
flow: null,
|
||||
flow: "forward",
|
||||
},
|
||||
defines: {
|
||||
family: "Structure", color: "#38bdf8", dash: "7 3", marker: "diamond-arrow",
|
||||
|
|
@ -2345,10 +2347,10 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
const family = svgElement("text", {y: 48, "text-anchor": "middle", class: "family"});
|
||||
family.textContent = short(node.family, 22);
|
||||
group.append(title, family);
|
||||
group.addEventListener("click", () => {
|
||||
group.addEventListener("click", (event) => {
|
||||
if (!state.suppressClick) {
|
||||
selectNode(node.node_id);
|
||||
showNodeCard(node.node_id);
|
||||
showNodeCard(node.node_id, event);
|
||||
}
|
||||
});
|
||||
group.addEventListener("contextmenu", (event) => {
|
||||
|
|
@ -2368,7 +2370,7 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
}
|
||||
svg.append(definitions, edgeLayer, nodeLayer);
|
||||
}
|
||||
function renderDetails(details, node, data, interactiveBadges = false) {
|
||||
function renderDetails(details, node, data, interactiveBadges = false, includeContent = true) {
|
||||
details.replaceChildren();
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "detail-head";
|
||||
|
|
@ -2416,9 +2418,12 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
row.append(dt, dd);
|
||||
dl.append(row);
|
||||
}
|
||||
const content = document.createElement("pre");
|
||||
content.textContent = node.content;
|
||||
details.append(heading, badges, summary, dl, content);
|
||||
details.append(heading, badges, summary, dl);
|
||||
if (includeContent) {
|
||||
const content = document.createElement("pre");
|
||||
content.textContent = node.content;
|
||||
details.append(content);
|
||||
}
|
||||
}
|
||||
function closeNodeDialog() {
|
||||
const dialog = $("node-dialog");
|
||||
|
|
@ -2430,18 +2435,39 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
if (dialog.open) dialog.close();
|
||||
else state.cardNode = null;
|
||||
}
|
||||
async function showNodeCard(nodeId) {
|
||||
function positionNodeCard(dialog, event) {
|
||||
const canvas = $("graph").getBoundingClientRect();
|
||||
const centerX = canvas.left + canvas.width / 2;
|
||||
const centerY = canvas.top + canvas.height / 2;
|
||||
const clientX = Number.isFinite(event?.clientX) ? event.clientX : centerX;
|
||||
const clientY = Number.isFinite(event?.clientY) ? event.clientY : centerY;
|
||||
const offset = 14;
|
||||
const margin = 8;
|
||||
const bounds = dialog.getBoundingClientRect();
|
||||
const maximumLeft = Math.max(margin, window.innerWidth - bounds.width - margin);
|
||||
const maximumTop = Math.max(margin, window.innerHeight - bounds.height - margin);
|
||||
const left = clamp(clientX + offset, margin, maximumLeft);
|
||||
const top = clamp(clientY + offset, margin, maximumTop);
|
||||
dialog.style.left = `${left}px`;
|
||||
dialog.style.top = `${top}px`;
|
||||
}
|
||||
async function showNodeCard(nodeId, event) {
|
||||
try {
|
||||
selectNode(nodeId);
|
||||
setStatus(`Loading descriptor for ${nodeId}…`);
|
||||
const params = new URLSearchParams({id: nodeId, depth: String(state.depth), limit: "100"});
|
||||
const data = await api(`node?${params}`);
|
||||
state.cardNode = nodeId;
|
||||
renderDetails($("node-card-details"), data.node, data, true);
|
||||
$("node-card-label").textContent = short(data.node.title, 72);
|
||||
renderDetails($("node-card-details"), data.node, data, true, false);
|
||||
const dialog = $("node-card");
|
||||
if (!dialog.open) dialog.showModal();
|
||||
$("close-node-card").focus();
|
||||
if (!dialog.open) {
|
||||
dialog.style.visibility = "hidden";
|
||||
dialog.show();
|
||||
positionNodeCard(dialog, event);
|
||||
dialog.style.visibility = "";
|
||||
} else {
|
||||
positionNodeCard(dialog, event);
|
||||
}
|
||||
setStatus(`Selected ${nodeId}`);
|
||||
} catch (error) {
|
||||
setStatus(error.message, true);
|
||||
|
|
@ -2635,8 +2661,6 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
$("reset-view").addEventListener("click", resetViewport);
|
||||
$("close-node-dialog").addEventListener("click", closeNodeDialog);
|
||||
$("dismiss-node-dialog").addEventListener("click", closeNodeDialog);
|
||||
$("close-node-card").addEventListener("click", closeNodeCard);
|
||||
$("dismiss-node-card").addEventListener("click", closeNodeCard);
|
||||
setupPanelResizer("left");
|
||||
setupPanelResizer("right");
|
||||
$("node-dialog").querySelector(".dialog-head").addEventListener("pointerdown", beginDialogDrag);
|
||||
|
|
@ -2668,13 +2692,6 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
setStatus(`${nodeCount} nodes · ${edgeCount} edges in neighborhood`);
|
||||
}
|
||||
});
|
||||
$("node-card").addEventListener("click", (event) => {
|
||||
if (event.target !== $("node-card")) return;
|
||||
const bounds = $("node-card").getBoundingClientRect();
|
||||
const inside = event.clientX >= bounds.left && event.clientX <= bounds.right
|
||||
&& event.clientY >= bounds.top && event.clientY <= bounds.bottom;
|
||||
if (!inside) closeNodeCard();
|
||||
});
|
||||
$("node-card").addEventListener("close", () => {
|
||||
state.cardNode = null;
|
||||
if (state.graph) {
|
||||
|
|
@ -2684,6 +2701,11 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
}
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && $("node-card").open) {
|
||||
event.preventDefault();
|
||||
closeNodeCard();
|
||||
return;
|
||||
}
|
||||
if (event.code !== "Space" || event.defaultPrevented
|
||||
|| $("node-dialog").open || $("node-card").open) {
|
||||
return;
|
||||
|
|
@ -2695,6 +2717,12 @@ _GRAPH_BROWSER_HTML = r"""<!DOCTYPE html>
|
|||
}
|
||||
if (centerSelectedNode()) event.preventDefault();
|
||||
});
|
||||
document.addEventListener("pointerdown", (event) => {
|
||||
const card = $("node-card");
|
||||
if (card.open && event.target instanceof Node && !card.contains(event.target)) {
|
||||
closeNodeCard();
|
||||
}
|
||||
});
|
||||
$("graph").addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
zoomAt(event.deltaY < 0 ? .85 : 1.18, event.clientX, event.clientY);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue