Implemented new InstancesDebugger.py. Based on the HierarchicalCanvasGraph.py
This commit is contained in:
@@ -274,18 +274,18 @@
|
||||
// Data
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
const NODES = [
|
||||
{ id: 'app', label: 'app', type: 'root', kind: 'RootInstance' },
|
||||
{ id: 'layout', label: 'layout', type: 'single', kind: 'Layout' },
|
||||
{ id: 'left_panel', label: 'left_panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'right_panel', label: 'right_panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'instances_debugger', label: 'instances_debugger', type: 'single', kind: 'InstancesDebugger' },
|
||||
{ id: 'dbg_panel', label: 'dbg#panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'canvas_graph', label: 'dbg#canvas_graph', type: 'multiple', kind: 'CanvasGraph' },
|
||||
{ id: 'data_grid_manager', label: 'data_grid_manager', type: 'single', kind: 'DataGridsManager' },
|
||||
{ id: 'my_grid', label: 'my_grid', type: 'multiple', kind: 'DataGrid' },
|
||||
{ id: 'grid_toolbar', label: 'my_grid#toolbar', type: 'multiple', kind: 'Toolbar' },
|
||||
{ id: 'grid_search', label: 'my_grid#search', type: 'multiple', kind: 'Search' },
|
||||
{ id: 'auth_proxy', label: 'auth_proxy', type: 'unique', kind: 'AuthProxy' },
|
||||
{ id: 'app', label: 'app', type: 'root', kind: 'RootInstance', description: 'Main application root' },
|
||||
{ id: 'layout', label: 'layout', type: 'single', kind: 'Layout', description: 'Main layout with 2 panels' },
|
||||
{ id: 'left_panel', label: 'left_panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'right_panel', label: 'right_panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'instances_debugger', label: 'instances_debugger', type: 'single', kind: 'InstancesDebugger', description: 'Debug tool for instances' },
|
||||
{ id: 'dbg_panel', label: 'dbg#panel', type: 'multiple', kind: 'Panel' },
|
||||
{ id: 'canvas_graph', label: 'dbg#canvas_graph', type: 'multiple', kind: 'CanvasGraph', description: 'Canvas-based graph view' },
|
||||
{ id: 'data_grid_manager', label: 'data_grid_manager', type: 'single', kind: 'DataGridsManager', description: 'Manages all data grids' },
|
||||
{ id: 'my_grid', label: 'my_grid', type: 'multiple', kind: 'DataGrid', description: '42 rows × 5 columns' },
|
||||
{ id: 'grid_toolbar', label: 'my_grid#toolbar', type: 'multiple', kind: 'Toolbar' },
|
||||
{ id: 'grid_search', label: 'my_grid#search', type: 'multiple', kind: 'Search' },
|
||||
{ id: 'auth_proxy', label: 'auth_proxy', type: 'unique', kind: 'AuthProxy', description: 'User authentication proxy' },
|
||||
];
|
||||
|
||||
const EDGES = [
|
||||
@@ -320,11 +320,16 @@ const DETAILS = {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Constants
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
const NODE_W = 178;
|
||||
const NODE_H = 36;
|
||||
const LEVEL_H = 84; // vertical distance between levels
|
||||
const LEAF_GAP = 22; // horizontal gap between leaf slots
|
||||
const CHEV_ZONE = 26; // rightmost px = toggle hit zone
|
||||
const NODE_W = 178;
|
||||
const NODE_H_SMALL = 36; // Without description
|
||||
const NODE_H_LARGE = 54; // With description
|
||||
const LEVEL_H = 96; // Vertical distance between levels
|
||||
const LEAF_GAP = 22; // Horizontal gap between leaf slots
|
||||
const CHEV_ZONE = 26; // Rightmost px = toggle hit zone
|
||||
|
||||
function getNodeHeight(node) {
|
||||
return node.description ? NODE_H_LARGE : NODE_H_SMALL;
|
||||
}
|
||||
|
||||
const TYPE_COLOR = {
|
||||
root: '#2563eb',
|
||||
@@ -469,8 +474,12 @@ function draw() {
|
||||
const p1 = pos[edge.from], p2 = pos[edge.to];
|
||||
if (!p1 || !p2) continue;
|
||||
const dimmed = matchIds && !matchIds.has(edge.from) && !matchIds.has(edge.to);
|
||||
const x1 = p1.x, y1 = p1.y + NODE_H / 2;
|
||||
const x2 = p2.x, y2 = p2.y - NODE_H / 2;
|
||||
const node1 = NODES.find(n => n.id === edge.from);
|
||||
const node2 = NODES.find(n => n.id === edge.to);
|
||||
const h1 = node1 ? getNodeHeight(node1) : NODE_H_SMALL;
|
||||
const h2 = node2 ? getNodeHeight(node2) : NODE_H_SMALL;
|
||||
const x1 = p1.x, y1 = p1.y + h1 / 2;
|
||||
const x2 = p2.x, y2 = p2.y - h2 / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
@@ -498,7 +507,8 @@ function draw() {
|
||||
|
||||
// ── Node renderer ────────────────────────────────────────
|
||||
function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
const hw = NODE_W / 2, hh = NODE_H / 2, r = 6;
|
||||
const nodeH = getNodeHeight(node);
|
||||
const hw = NODE_W / 2, hh = nodeH / 2, r = 6;
|
||||
const x = cx - hw, y = cy - hh;
|
||||
const color = TYPE_COLOR[node.type] || '#334155';
|
||||
|
||||
@@ -509,7 +519,7 @@ function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
|
||||
// Background — dark card
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, NODE_W, NODE_H, r);
|
||||
ctx.roundRect(x, y, NODE_W, nodeH, r);
|
||||
ctx.fillStyle = isSel ? '#2a1f0f' : '#1c2128';
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
@@ -517,15 +527,15 @@ function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
// Left color strip (clipped)
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, NODE_W, NODE_H, r);
|
||||
ctx.roundRect(x, y, NODE_W, nodeH, r);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x, y, 4, NODE_H);
|
||||
ctx.fillRect(x, y, 4, nodeH);
|
||||
ctx.restore();
|
||||
|
||||
// Border
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, NODE_W, NODE_H, r);
|
||||
ctx.roundRect(x, y, NODE_W, nodeH, r);
|
||||
if (isSel) {
|
||||
ctx.strokeStyle = '#f0883e';
|
||||
ctx.lineWidth = 1.5;
|
||||
@@ -545,7 +555,7 @@ function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
const badgeW = Math.min(rawW + 8, 66);
|
||||
const chevSpace = hasChildren(node.id) ? CHEV_ZONE : 8;
|
||||
const badgeX = x + NODE_W - chevSpace - badgeW - 2;
|
||||
const badgeY = y + (NODE_H - 14) / 2;
|
||||
const badgeY = y + (nodeH - 14) / 2;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(badgeX, badgeY, badgeW, 14, 3);
|
||||
ctx.fillStyle = `${color}22`;
|
||||
@@ -559,7 +569,7 @@ function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
if (kLabel !== kindText) kLabel += '…';
|
||||
ctx.fillText(kLabel, badgeX + badgeW / 2, badgeY + 7);
|
||||
|
||||
// Label
|
||||
// Label (centered if no description, top if description)
|
||||
ctx.font = `${isSel ? 500 : 400} 12px monospace`;
|
||||
ctx.fillStyle = isDim ? 'rgba(125,133,144,0.5)' : '#e6edf3';
|
||||
ctx.textAlign = 'left';
|
||||
@@ -569,7 +579,18 @@ function drawNode(node, cx, cy, isSel, isMatch, isDim) {
|
||||
let label = node.label;
|
||||
while (label.length > 3 && ctx.measureText(label).width > labelMaxW) label = label.slice(0, -1);
|
||||
if (label !== node.label) label += '…';
|
||||
ctx.fillText(label, labelX, cy);
|
||||
const labelY = node.description ? cy - 9 : cy;
|
||||
ctx.fillText(label, labelX, labelY);
|
||||
|
||||
// Description (bottom line, only if present)
|
||||
if (node.description) {
|
||||
ctx.font = '9px system-ui';
|
||||
ctx.fillStyle = isDim ? 'rgba(125,133,144,0.3)' : 'rgba(125,133,144,0.7)';
|
||||
let desc = node.description;
|
||||
while (desc.length > 3 && ctx.measureText(desc).width > labelMaxW) desc = desc.slice(0, -1);
|
||||
if (desc !== node.description) desc += '…';
|
||||
ctx.fillText(desc, labelX, cy + 8);
|
||||
}
|
||||
|
||||
// Chevron toggle (if has children)
|
||||
if (hasChildren(node.id)) {
|
||||
@@ -606,13 +627,18 @@ function drawChevron(ctx, cx, cy, pointDown, color) {
|
||||
function fitAll() {
|
||||
const vn = visNodes();
|
||||
if (vn.length === 0) return;
|
||||
const xs = vn.map(n => pos[n.id]?.x ?? 0);
|
||||
const ys = vn.map(n => pos[n.id]?.y ?? 0);
|
||||
const pad = 48;
|
||||
const minX = Math.min(...xs) - NODE_W / 2 - pad;
|
||||
const maxX = Math.max(...xs) + NODE_W / 2 + pad;
|
||||
const minY = Math.min(...ys) - NODE_H / 2 - pad;
|
||||
const maxY = Math.max(...ys) + NODE_H / 2 + pad;
|
||||
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
||||
for (const n of vn) {
|
||||
const p = pos[n.id];
|
||||
if (!p) continue;
|
||||
const h = getNodeHeight(n);
|
||||
minX = Math.min(minX, p.x - NODE_W / 2);
|
||||
maxX = Math.max(maxX, p.x + NODE_W / 2);
|
||||
minY = Math.min(minY, p.y - h / 2);
|
||||
maxY = Math.max(maxY, p.y + h / 2);
|
||||
}
|
||||
minX -= pad; maxX += pad; minY -= pad; maxY += pad;
|
||||
const scale = Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY), 1.5);
|
||||
transform.scale = scale;
|
||||
transform.x = (canvas.width - (minX + maxX) * scale) / 2;
|
||||
@@ -631,7 +657,8 @@ function hitTest(sx, sy) {
|
||||
const n = vn[i];
|
||||
const p = pos[n.id];
|
||||
if (!p) continue;
|
||||
if (Math.abs(wx - p.x) <= NODE_W / 2 && Math.abs(wy - p.y) <= NODE_H / 2) {
|
||||
const nodeH = getNodeHeight(n);
|
||||
if (Math.abs(wx - p.x) <= NODE_W / 2 && Math.abs(wy - p.y) <= nodeH / 2) {
|
||||
const isToggle = hasChildren(n.id) && wx >= p.x + NODE_W / 2 - CHEV_ZONE;
|
||||
return { node: n, isToggle };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user