@reqlan/analytical 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/out/analysis/contextual-search.d.ts +26 -0
- package/out/analysis/contextual-search.js +94 -0
- package/out/analysis/contextual-search.js.map +1 -0
- package/out/analysis/fuzzy-search.d.ts +24 -0
- package/out/analysis/fuzzy-search.js +254 -0
- package/out/analysis/fuzzy-search.js.map +1 -1
- package/out/analysis/git-dates-analyser.d.ts +3 -0
- package/out/analysis/git-dates-analyser.js +32 -13
- package/out/analysis/git-dates-analyser.js.map +1 -1
- package/out/analysis-api.d.ts +5 -1
- package/out/analysis-api.js +12 -3
- package/out/analysis-api.js.map +1 -1
- package/out/core/base-discovery.js +10 -3
- package/out/core/base-discovery.js.map +1 -1
- package/out/core/context-signals.d.ts +5 -0
- package/out/core/context-signals.js +13 -4
- package/out/core/context-signals.js.map +1 -1
- package/out/core/file-reference-resolve.js +6 -2
- package/out/core/file-reference-resolve.js.map +1 -1
- package/out/core/types.d.ts +8 -0
- package/out/core/types.js.map +1 -1
- package/out/core/workspace-paths.d.ts +1 -0
- package/out/core/workspace-paths.js +9 -5
- package/out/core/workspace-paths.js.map +1 -1
- package/out/export/html-export-assets.d.ts +1 -1
- package/out/export/html-export-assets.js +388 -20
- package/out/export/html-export-assets.js.map +1 -1
- package/out/export/html-export-template.js +15 -0
- package/out/export/html-export-template.js.map +1 -1
- package/out/index-store/file-treatment.d.ts +40 -0
- package/out/index-store/file-treatment.js +144 -0
- package/out/index-store/file-treatment.js.map +1 -0
- package/out/index-store/schema.d.ts +1 -1
- package/out/index-store/schema.js +6 -2
- package/out/index-store/schema.js.map +1 -1
- package/out/index-store/sqlite-store.d.ts +4 -2
- package/out/index-store/sqlite-store.js +39 -21
- package/out/index-store/sqlite-store.js.map +1 -1
- package/out/index-store/webview-graph-queries.d.ts +2 -0
- package/out/index-store/webview-graph-queries.js.map +1 -1
- package/out/index-store/webview-table-queries.d.ts +1 -1
- package/out/index-store/webview-table-queries.js +9 -0
- package/out/index-store/webview-table-queries.js.map +1 -1
- package/out/index-store/workspace-index-file.js +2 -6
- package/out/index-store/workspace-index-file.js.map +1 -1
- package/out/index.d.ts +8 -4
- package/out/index.js +4 -2
- package/out/index.js.map +1 -1
- package/package.json +10 -2
|
@@ -309,6 +309,27 @@ a.pill:hover { color: var(--accent-strong); border-color: color-mix(in srgb, var
|
|
|
309
309
|
border-color: var(--rust);
|
|
310
310
|
color: var(--fg);
|
|
311
311
|
}
|
|
312
|
+
.graph-file-treatment {
|
|
313
|
+
display: inline-flex;
|
|
314
|
+
align-items: center;
|
|
315
|
+
}
|
|
316
|
+
.graph-file-treatment .graph-select,
|
|
317
|
+
select.graph-action {
|
|
318
|
+
border-radius: 8px;
|
|
319
|
+
padding-right: 1.6rem;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
}
|
|
322
|
+
.visually-hidden {
|
|
323
|
+
position: absolute;
|
|
324
|
+
width: 1px;
|
|
325
|
+
height: 1px;
|
|
326
|
+
padding: 0;
|
|
327
|
+
margin: -1px;
|
|
328
|
+
overflow: hidden;
|
|
329
|
+
clip: rect(0, 0, 0, 0);
|
|
330
|
+
white-space: nowrap;
|
|
331
|
+
border: 0;
|
|
332
|
+
}
|
|
312
333
|
.graph-status {
|
|
313
334
|
color: var(--muted);
|
|
314
335
|
font-size: 0.92rem;
|
|
@@ -1200,6 +1221,80 @@ const GRAPH_HIT_PAD = 6;
|
|
|
1200
1221
|
const GRAPH_LABEL_FADE_START = 0.62;
|
|
1201
1222
|
const GRAPH_LABEL_FADE_END = 0.82;
|
|
1202
1223
|
const GRAPH_LABEL_MODES = ['auto', 'on', 'off'];
|
|
1224
|
+
const FILE_TREATMENT_MODES = ['invisible', 'compound', 'linked'];
|
|
1225
|
+
|
|
1226
|
+
function fileIdeasetNodeId(fileUri) {
|
|
1227
|
+
return 'rq-file:' + fileUri;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function isFileIdeasetNode(node) {
|
|
1231
|
+
return Boolean(node && (node.isFileIdeaset || String(node.id || '').startsWith('rq-file:')));
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function fileIdeasetDisplayName(fileUri) {
|
|
1235
|
+
const trimmed = String(fileUri || '').replace(/\\\\/g, '/');
|
|
1236
|
+
const slash = trimmed.lastIndexOf('/');
|
|
1237
|
+
const fileName = slash >= 0 ? trimmed.slice(slash + 1) : trimmed;
|
|
1238
|
+
return fileName.endsWith('.rq') ? fileName.slice(0, -3) : fileName;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function fileTreatmentLabel(mode) {
|
|
1242
|
+
if (mode === 'compound') return 'Files: compound';
|
|
1243
|
+
if (mode === 'linked') return 'Files: linked';
|
|
1244
|
+
return 'Files: hidden';
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function applyFileTreatment(graph, treatment) {
|
|
1248
|
+
const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
|
|
1249
|
+
const edges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
1250
|
+
const baseNodes = nodes.filter(node => !isFileIdeasetNode(node));
|
|
1251
|
+
const keep = new Set(baseNodes.map(node => node.id));
|
|
1252
|
+
const baseEdges = edges.filter(edge => keep.has(edge.sourceId) && keep.has(edge.targetId));
|
|
1253
|
+
if (treatment !== 'linked') {
|
|
1254
|
+
return { ...graph, nodes: baseNodes, edges: baseEdges };
|
|
1255
|
+
}
|
|
1256
|
+
const membersByFile = new Map();
|
|
1257
|
+
for (const node of baseNodes) {
|
|
1258
|
+
if (node.isExternal || !node.fileUri) continue;
|
|
1259
|
+
const list = membersByFile.get(node.fileUri) || [];
|
|
1260
|
+
list.push(node);
|
|
1261
|
+
membersByFile.set(node.fileUri, list);
|
|
1262
|
+
}
|
|
1263
|
+
const extraNodes = [];
|
|
1264
|
+
const extraEdges = [];
|
|
1265
|
+
for (const fileUri of [...membersByFile.keys()].sort()) {
|
|
1266
|
+
const members = membersByFile.get(fileUri) || [];
|
|
1267
|
+
if (!members.length) continue;
|
|
1268
|
+
const id = fileIdeasetNodeId(fileUri);
|
|
1269
|
+
const name = fileIdeasetDisplayName(fileUri);
|
|
1270
|
+
extraNodes.push({
|
|
1271
|
+
id,
|
|
1272
|
+
name,
|
|
1273
|
+
kind: 'ideaset',
|
|
1274
|
+
fileUri,
|
|
1275
|
+
lineStart: 0,
|
|
1276
|
+
tags: [],
|
|
1277
|
+
isFileIdeaset: true,
|
|
1278
|
+
pageUrl: members.find(member => member.hostFilePageUrl)?.hostFilePageUrl
|
|
1279
|
+
|| members.find(member => member.pageUrl)?.pageUrl
|
|
1280
|
+
|| undefined
|
|
1281
|
+
});
|
|
1282
|
+
for (const member of members) {
|
|
1283
|
+
extraEdges.push({
|
|
1284
|
+
id: id + '->ideaset_member:' + member.id,
|
|
1285
|
+
sourceId: id,
|
|
1286
|
+
targetId: member.id,
|
|
1287
|
+
kind: 'ideaset_member',
|
|
1288
|
+
label: name
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
...graph,
|
|
1294
|
+
nodes: baseNodes.concat(extraNodes),
|
|
1295
|
+
edges: baseEdges.concat(extraEdges)
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1203
1298
|
|
|
1204
1299
|
function wireGraph(root) {
|
|
1205
1300
|
const graphRoots = root.querySelectorAll('[data-graph-json]');
|
|
@@ -1227,6 +1322,7 @@ function wireGraph(root) {
|
|
|
1227
1322
|
const tagHost = controls.querySelector('[data-graph-tag-scd]');
|
|
1228
1323
|
const toggleExternal = controls.querySelector('[data-graph-toggle-external]');
|
|
1229
1324
|
const toggleIdeasets = controls.querySelector('[data-graph-toggle-ideasets]');
|
|
1325
|
+
const fileTreatmentSelect = controls.querySelector('[data-graph-file-treatment]');
|
|
1230
1326
|
const toggleWildcard = controls.querySelector('[data-graph-toggle-wildcard]');
|
|
1231
1327
|
const toggleLabels = controls.querySelector('[data-graph-toggle-labels]');
|
|
1232
1328
|
const togglePhysics = controls.querySelector('[data-graph-toggle-physics]');
|
|
@@ -1237,6 +1333,7 @@ function wireGraph(root) {
|
|
|
1237
1333
|
element.classList.add('is-booting');
|
|
1238
1334
|
let hideExternal = false;
|
|
1239
1335
|
let hideIdeasets = false;
|
|
1336
|
+
let fileTreatment = fileTreatmentSelect?.value || 'linked';
|
|
1240
1337
|
let includeWildcardRefs = true;
|
|
1241
1338
|
let labelMode = 'auto';
|
|
1242
1339
|
let statusSelected = [];
|
|
@@ -1253,6 +1350,10 @@ function wireGraph(root) {
|
|
|
1253
1350
|
tagSelected = tagFilter.getSelected();
|
|
1254
1351
|
let livePhysics = false;
|
|
1255
1352
|
let animationFrame = 0;
|
|
1353
|
+
let settleFrame = 0;
|
|
1354
|
+
let settleGeneration = 0;
|
|
1355
|
+
// While true, auto labels stay at opacity 0 so text paint does not lag settle.
|
|
1356
|
+
let settlingLayout = false;
|
|
1256
1357
|
let calmTicks = 0;
|
|
1257
1358
|
let simNodes = [];
|
|
1258
1359
|
let simEdges = [];
|
|
@@ -1266,12 +1367,15 @@ function wireGraph(root) {
|
|
|
1266
1367
|
let view = { x: 0, y: 0, w: 1200, h: 640 };
|
|
1267
1368
|
let userViewport = false;
|
|
1268
1369
|
let dragNodeId = null;
|
|
1370
|
+
let dragCompound = null;
|
|
1269
1371
|
let dragMoved = false;
|
|
1270
1372
|
let panMode = false;
|
|
1271
1373
|
let panOrigin = null;
|
|
1272
1374
|
let suppressClickUntil = 0;
|
|
1273
1375
|
let hoverNodeId = null;
|
|
1376
|
+
let hoverCompound = null;
|
|
1274
1377
|
let pulsePhase = 0;
|
|
1378
|
+
let compoundRegions = [];
|
|
1275
1379
|
|
|
1276
1380
|
function resolveGraphNodeUrl(pageUrl) {
|
|
1277
1381
|
let cleaned = String(pageUrl || '').replace(/^\\.\\//, '');
|
|
@@ -1493,12 +1597,93 @@ function wireGraph(root) {
|
|
|
1493
1597
|
ctx.fillRect(0, 0, cssWidth, cssHeight);
|
|
1494
1598
|
|
|
1495
1599
|
const { scale, offsetX, offsetY } = viewportTransform();
|
|
1496
|
-
|
|
1600
|
+
// Auto labels stay invisible during batch settle ([graph_label_auto] / html_export_graph_label_auto).
|
|
1601
|
+
const ambientLabelOpacity =
|
|
1602
|
+
settlingLayout && labelMode === 'auto' ? 0 : labelOpacityAtScale(scale);
|
|
1497
1603
|
ctx.save();
|
|
1498
1604
|
ctx.translate(offsetX, offsetY);
|
|
1499
1605
|
ctx.scale(scale, scale);
|
|
1500
1606
|
ctx.translate(-view.x, -view.y);
|
|
1501
1607
|
|
|
1608
|
+
if (fileTreatment === 'compound') {
|
|
1609
|
+
compoundRegions = [];
|
|
1610
|
+
const byFile = new Map();
|
|
1611
|
+
for (const node of simNodes) {
|
|
1612
|
+
if (node.isExternal || isFileIdeasetNode(node) || !node.fileUri) continue;
|
|
1613
|
+
const list = byFile.get(node.fileUri) || [];
|
|
1614
|
+
list.push(node);
|
|
1615
|
+
byFile.set(node.fileUri, list);
|
|
1616
|
+
}
|
|
1617
|
+
for (const [fileUri, members] of byFile) {
|
|
1618
|
+
let minX = Infinity;
|
|
1619
|
+
let minY = Infinity;
|
|
1620
|
+
let maxX = -Infinity;
|
|
1621
|
+
let maxY = -Infinity;
|
|
1622
|
+
let any = false;
|
|
1623
|
+
let pageUrl;
|
|
1624
|
+
for (const node of members) {
|
|
1625
|
+
const pos = positions.get(node.id);
|
|
1626
|
+
if (!pos) continue;
|
|
1627
|
+
const r = nodeRadius(node) + 14;
|
|
1628
|
+
any = true;
|
|
1629
|
+
minX = Math.min(minX, pos.x - r);
|
|
1630
|
+
minY = Math.min(minY, pos.y - r);
|
|
1631
|
+
maxX = Math.max(maxX, pos.x + r);
|
|
1632
|
+
maxY = Math.max(maxY, pos.y + r);
|
|
1633
|
+
pageUrl = pageUrl || node.hostFilePageUrl || node.pageUrl;
|
|
1634
|
+
}
|
|
1635
|
+
if (!any) continue;
|
|
1636
|
+
const pad = 8;
|
|
1637
|
+
const x = minX - pad;
|
|
1638
|
+
const y = minY - pad;
|
|
1639
|
+
const w = (maxX - minX) + pad * 2;
|
|
1640
|
+
const h = (maxY - minY) + pad * 2;
|
|
1641
|
+
const titleH = 18;
|
|
1642
|
+
const label = fileIdeasetDisplayName(fileUri);
|
|
1643
|
+
compoundRegions.push({
|
|
1644
|
+
fileUri,
|
|
1645
|
+
pageUrl,
|
|
1646
|
+
memberIds: members.map(member => member.id),
|
|
1647
|
+
x,
|
|
1648
|
+
y,
|
|
1649
|
+
w,
|
|
1650
|
+
h,
|
|
1651
|
+
titleY: y - titleH,
|
|
1652
|
+
titleH,
|
|
1653
|
+
label
|
|
1654
|
+
});
|
|
1655
|
+
const radius = 10;
|
|
1656
|
+
const hover = hoverCompound && hoverCompound.fileUri === fileUri;
|
|
1657
|
+
ctx.beginPath();
|
|
1658
|
+
ctx.moveTo(x + radius, y);
|
|
1659
|
+
ctx.arcTo(x + w, y, x + w, y + h, radius);
|
|
1660
|
+
ctx.arcTo(x + w, y + h, x, y + h, radius);
|
|
1661
|
+
ctx.arcTo(x, y + h, x, y, radius);
|
|
1662
|
+
ctx.arcTo(x, y, x + w, y, radius);
|
|
1663
|
+
ctx.closePath();
|
|
1664
|
+
ctx.fillStyle = hover ? 'rgba(209, 134, 22, 0.14)' : 'rgba(209, 134, 22, 0.08)';
|
|
1665
|
+
ctx.fill();
|
|
1666
|
+
ctx.strokeStyle = hover ? 'rgba(224, 162, 74, 0.85)' : 'rgba(209, 134, 22, 0.45)';
|
|
1667
|
+
ctx.lineWidth = (hover ? 1.8 : 1.2) / scale;
|
|
1668
|
+
ctx.stroke();
|
|
1669
|
+
ctx.fillStyle = 'rgba(224, 162, 74, 0.95)';
|
|
1670
|
+
ctx.font = \`600 \${11 / scale}px "IBM Plex Sans", system-ui, sans-serif\`;
|
|
1671
|
+
ctx.textAlign = 'left';
|
|
1672
|
+
ctx.textBaseline = 'bottom';
|
|
1673
|
+
ctx.fillText(label, x + 6, y - 4 / scale);
|
|
1674
|
+
// Underline title to signal clickability.
|
|
1675
|
+
const titleWidth = ctx.measureText(label).width;
|
|
1676
|
+
ctx.beginPath();
|
|
1677
|
+
ctx.moveTo(x + 6, y - 2 / scale);
|
|
1678
|
+
ctx.lineTo(x + 6 + titleWidth, y - 2 / scale);
|
|
1679
|
+
ctx.strokeStyle = 'rgba(224, 162, 74, 0.85)';
|
|
1680
|
+
ctx.lineWidth = 1 / scale;
|
|
1681
|
+
ctx.stroke();
|
|
1682
|
+
}
|
|
1683
|
+
} else {
|
|
1684
|
+
compoundRegions = [];
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1502
1687
|
ctx.lineWidth = 1.1 / scale;
|
|
1503
1688
|
ctx.strokeStyle = 'rgba(61,47,40,0.85)';
|
|
1504
1689
|
ctx.globalAlpha = 0.85;
|
|
@@ -1585,6 +1770,14 @@ function wireGraph(root) {
|
|
|
1585
1770
|
}
|
|
1586
1771
|
}
|
|
1587
1772
|
|
|
1773
|
+
function cancelSettle() {
|
|
1774
|
+
settleGeneration += 1;
|
|
1775
|
+
if (settleFrame) {
|
|
1776
|
+
cancelAnimationFrame(settleFrame);
|
|
1777
|
+
settleFrame = 0;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1588
1781
|
function scheduleTick() {
|
|
1589
1782
|
if (animationFrame) return;
|
|
1590
1783
|
animationFrame = requestAnimationFrame(() => {
|
|
@@ -1612,16 +1805,54 @@ function wireGraph(root) {
|
|
|
1612
1805
|
if (livePhysics) scheduleTick();
|
|
1613
1806
|
}
|
|
1614
1807
|
|
|
1615
|
-
function
|
|
1808
|
+
function settleChunkSize(nodeCount) {
|
|
1809
|
+
if (nodeCount > 400) return 1;
|
|
1810
|
+
if (nodeCount > 200) return 2;
|
|
1811
|
+
if (nodeCount > 80) return 4;
|
|
1812
|
+
return 8;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function batchSettleAsync(nodes, edges, onDone) {
|
|
1816
|
+
cancelSettle();
|
|
1817
|
+
const generation = settleGeneration;
|
|
1616
1818
|
const iterations = Math.min(96, 36 + Math.floor(nodes.length * 0.45));
|
|
1617
|
-
|
|
1618
|
-
|
|
1819
|
+
const chunkSize = settleChunkSize(nodes.length);
|
|
1820
|
+
let step = 0;
|
|
1821
|
+
|
|
1822
|
+
function finish() {
|
|
1823
|
+
if (generation !== settleGeneration) return;
|
|
1824
|
+
for (const velocity of velocities.values()) {
|
|
1825
|
+
velocity.vx = 0;
|
|
1826
|
+
velocity.vy = 0;
|
|
1827
|
+
}
|
|
1828
|
+
calmTicks = EXPORT_PHYSICS_SETTINGS.restTicks;
|
|
1829
|
+
settleFrame = 0;
|
|
1830
|
+
onDone();
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function runChunk() {
|
|
1834
|
+
if (generation !== settleGeneration) return;
|
|
1835
|
+
const end = Math.min(step + chunkSize, iterations);
|
|
1836
|
+
for (; step < end; step += 1) {
|
|
1837
|
+
physicsStep(nodes, edges);
|
|
1838
|
+
}
|
|
1839
|
+
paint();
|
|
1840
|
+
if (statusText && iterations > 0) {
|
|
1841
|
+
const pct = Math.min(99, Math.round((step / iterations) * 100));
|
|
1842
|
+
statusText.textContent = \`Settling layout… \${pct}%\`;
|
|
1843
|
+
}
|
|
1844
|
+
if (step >= iterations) {
|
|
1845
|
+
finish();
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
settleFrame = requestAnimationFrame(runChunk);
|
|
1619
1849
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1850
|
+
|
|
1851
|
+
if (iterations <= 0) {
|
|
1852
|
+
finish();
|
|
1853
|
+
return;
|
|
1623
1854
|
}
|
|
1624
|
-
|
|
1855
|
+
settleFrame = requestAnimationFrame(runChunk);
|
|
1625
1856
|
}
|
|
1626
1857
|
|
|
1627
1858
|
function hitTest(point) {
|
|
@@ -1642,6 +1873,29 @@ function wireGraph(root) {
|
|
|
1642
1873
|
return best;
|
|
1643
1874
|
}
|
|
1644
1875
|
|
|
1876
|
+
function hitTestCompound(point) {
|
|
1877
|
+
// Prefer title hits, then body (for drag).
|
|
1878
|
+
let bodyHit = null;
|
|
1879
|
+
for (let i = compoundRegions.length - 1; i >= 0; i -= 1) {
|
|
1880
|
+
const region = compoundRegions[i];
|
|
1881
|
+
const inTitle = point.x >= region.x
|
|
1882
|
+
&& point.x <= region.x + region.w
|
|
1883
|
+
&& point.y >= region.titleY
|
|
1884
|
+
&& point.y <= region.y + 2;
|
|
1885
|
+
if (inTitle) {
|
|
1886
|
+
return { region, titleHit: true };
|
|
1887
|
+
}
|
|
1888
|
+
const inBody = point.x >= region.x
|
|
1889
|
+
&& point.x <= region.x + region.w
|
|
1890
|
+
&& point.y >= region.y
|
|
1891
|
+
&& point.y <= region.y + region.h;
|
|
1892
|
+
if (inBody && !bodyHit) {
|
|
1893
|
+
bodyHit = { region, titleHit: false };
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return bodyHit;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1645
1899
|
function resizeCanvas() {
|
|
1646
1900
|
const rect = element.getBoundingClientRect();
|
|
1647
1901
|
cssWidth = Math.max(320, Math.floor(rect.width || 1200));
|
|
@@ -1654,6 +1908,7 @@ function wireGraph(root) {
|
|
|
1654
1908
|
|
|
1655
1909
|
function mountGraph(filteredNodes, filteredEdges) {
|
|
1656
1910
|
stopLoop();
|
|
1911
|
+
cancelSettle();
|
|
1657
1912
|
simNodes = filteredNodes;
|
|
1658
1913
|
simEdges = filteredEdges;
|
|
1659
1914
|
const validIds = new Set(filteredNodes.map(node => node.id));
|
|
@@ -1670,15 +1925,22 @@ function wireGraph(root) {
|
|
|
1670
1925
|
element.appendChild(canvas);
|
|
1671
1926
|
resizeCanvas();
|
|
1672
1927
|
wireViewport(canvas);
|
|
1673
|
-
if (statusText) {
|
|
1674
|
-
statusText.textContent = \`\${filteredNodes.length} nodes, \${filteredEdges.length} edges\`;
|
|
1675
|
-
}
|
|
1676
1928
|
userViewport = false;
|
|
1929
|
+
const finishStatus = () => {
|
|
1930
|
+
settlingLayout = false;
|
|
1931
|
+
if (statusText) {
|
|
1932
|
+
statusText.textContent = \`\${filteredNodes.length} nodes, \${filteredEdges.length} edges\`;
|
|
1933
|
+
}
|
|
1934
|
+
paint();
|
|
1935
|
+
};
|
|
1677
1936
|
if (!livePhysics) {
|
|
1678
|
-
|
|
1937
|
+
settlingLayout = true;
|
|
1938
|
+
if (statusText) statusText.textContent = 'Settling layout…';
|
|
1679
1939
|
paint();
|
|
1940
|
+
batchSettleAsync(filteredNodes, filteredEdges, finishStatus);
|
|
1680
1941
|
} else {
|
|
1681
|
-
|
|
1942
|
+
settlingLayout = false;
|
|
1943
|
+
finishStatus();
|
|
1682
1944
|
wake();
|
|
1683
1945
|
}
|
|
1684
1946
|
}
|
|
@@ -1713,6 +1975,7 @@ function wireGraph(root) {
|
|
|
1713
1975
|
const hit = hitTest(point);
|
|
1714
1976
|
if (hit) {
|
|
1715
1977
|
dragNodeId = hit.id;
|
|
1978
|
+
dragCompound = null;
|
|
1716
1979
|
dragMoved = false;
|
|
1717
1980
|
pinnedIds.add(hit.id);
|
|
1718
1981
|
velocities.set(hit.id, { vx: 0, vy: 0 });
|
|
@@ -1722,6 +1985,31 @@ function wireGraph(root) {
|
|
|
1722
1985
|
paint();
|
|
1723
1986
|
return;
|
|
1724
1987
|
}
|
|
1988
|
+
const compoundHit = hitTestCompound(point);
|
|
1989
|
+
if (compoundHit) {
|
|
1990
|
+
dragCompound = {
|
|
1991
|
+
...compoundHit.region,
|
|
1992
|
+
titleHit: compoundHit.titleHit,
|
|
1993
|
+
origin: point,
|
|
1994
|
+
memberOrigins: Object.fromEntries(
|
|
1995
|
+
compoundHit.region.memberIds.map(id => {
|
|
1996
|
+
const pos = positions.get(id) || { x: 0, y: 0 };
|
|
1997
|
+
return [id, { x: pos.x, y: pos.y }];
|
|
1998
|
+
})
|
|
1999
|
+
)
|
|
2000
|
+
};
|
|
2001
|
+
dragNodeId = null;
|
|
2002
|
+
dragMoved = false;
|
|
2003
|
+
for (const id of compoundHit.region.memberIds) {
|
|
2004
|
+
pinnedIds.add(id);
|
|
2005
|
+
velocities.set(id, { vx: 0, vy: 0 });
|
|
2006
|
+
}
|
|
2007
|
+
target.classList.add('is-dragging-node');
|
|
2008
|
+
target.setPointerCapture(event.pointerId);
|
|
2009
|
+
event.preventDefault();
|
|
2010
|
+
paint();
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
1725
2013
|
panMode = true;
|
|
1726
2014
|
panOrigin = { x: event.clientX, y: event.clientY, viewX: view.x, viewY: view.y };
|
|
1727
2015
|
target.classList.add('is-panning');
|
|
@@ -1738,6 +2026,22 @@ function wireGraph(root) {
|
|
|
1738
2026
|
paint();
|
|
1739
2027
|
return;
|
|
1740
2028
|
}
|
|
2029
|
+
if (dragCompound) {
|
|
2030
|
+
const point = clientToGraph(event);
|
|
2031
|
+
const dx = point.x - dragCompound.origin.x;
|
|
2032
|
+
const dy = point.y - dragCompound.origin.y;
|
|
2033
|
+
if (Math.abs(dx) + Math.abs(dy) > 1) {
|
|
2034
|
+
dragMoved = true;
|
|
2035
|
+
}
|
|
2036
|
+
for (const id of dragCompound.memberIds) {
|
|
2037
|
+
const origin = dragCompound.memberOrigins[id];
|
|
2038
|
+
if (!origin) continue;
|
|
2039
|
+
positions.set(id, { x: origin.x + dx, y: origin.y + dy });
|
|
2040
|
+
}
|
|
2041
|
+
wake();
|
|
2042
|
+
paint();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
1741
2045
|
if (panMode && panOrigin) {
|
|
1742
2046
|
const rect = target.getBoundingClientRect();
|
|
1743
2047
|
const { scale } = viewportTransform();
|
|
@@ -1749,10 +2053,14 @@ function wireGraph(root) {
|
|
|
1749
2053
|
paint();
|
|
1750
2054
|
return;
|
|
1751
2055
|
}
|
|
1752
|
-
const
|
|
2056
|
+
const point = clientToGraph(event);
|
|
2057
|
+
const hit = hitTest(point);
|
|
1753
2058
|
const nextHover = hit?.id || null;
|
|
1754
|
-
|
|
2059
|
+
const compoundHit = hit ? null : hitTestCompound(point);
|
|
2060
|
+
const nextCompound = compoundHit?.region || null;
|
|
2061
|
+
if (nextHover !== hoverNodeId || nextCompound?.fileUri !== hoverCompound?.fileUri) {
|
|
1755
2062
|
hoverNodeId = nextHover;
|
|
2063
|
+
hoverCompound = nextCompound;
|
|
1756
2064
|
paint();
|
|
1757
2065
|
}
|
|
1758
2066
|
});
|
|
@@ -1774,6 +2082,23 @@ function wireGraph(root) {
|
|
|
1774
2082
|
}
|
|
1775
2083
|
return;
|
|
1776
2084
|
}
|
|
2085
|
+
if (dragCompound) {
|
|
2086
|
+
const compound = dragCompound;
|
|
2087
|
+
const moved = dragMoved;
|
|
2088
|
+
const openTitle = !moved && compound.titleHit;
|
|
2089
|
+
for (const id of compound.memberIds) {
|
|
2090
|
+
pinnedIds.delete(id);
|
|
2091
|
+
}
|
|
2092
|
+
dragCompound = null;
|
|
2093
|
+
wake();
|
|
2094
|
+
paint();
|
|
2095
|
+
if (openTitle && compound.pageUrl && Date.now() > suppressClickUntil) {
|
|
2096
|
+
window.location.href = resolveGraphNodeUrl(compound.pageUrl);
|
|
2097
|
+
} else if (moved) {
|
|
2098
|
+
suppressClickUntil = Date.now() + 250;
|
|
2099
|
+
}
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
1777
2102
|
if (panMode) {
|
|
1778
2103
|
panMode = false;
|
|
1779
2104
|
panOrigin = null;
|
|
@@ -1788,6 +2113,13 @@ function wireGraph(root) {
|
|
|
1788
2113
|
dragNodeId = null;
|
|
1789
2114
|
wake();
|
|
1790
2115
|
}
|
|
2116
|
+
if (dragCompound) {
|
|
2117
|
+
for (const id of dragCompound.memberIds) {
|
|
2118
|
+
pinnedIds.delete(id);
|
|
2119
|
+
}
|
|
2120
|
+
dragCompound = null;
|
|
2121
|
+
wake();
|
|
2122
|
+
}
|
|
1791
2123
|
panMode = false;
|
|
1792
2124
|
panOrigin = null;
|
|
1793
2125
|
target.classList.remove('is-panning');
|
|
@@ -1805,7 +2137,7 @@ function wireGraph(root) {
|
|
|
1805
2137
|
const query = lower(searchInput?.value?.trim());
|
|
1806
2138
|
const pathQuery = lower(pathInput?.value?.trim());
|
|
1807
2139
|
if (hideExternal && node.isExternal) return false;
|
|
1808
|
-
if (hideIdeasets && node.kind === 'ideaset') return false;
|
|
2140
|
+
if (hideIdeasets && node.kind === 'ideaset' && !isFileIdeasetNode(node)) return false;
|
|
1809
2141
|
const haystack = [node.name, node.kind, node.fileUri, node.status || '', ...(node.tags || [])].map(lower).join(' ');
|
|
1810
2142
|
if (query && !haystack.includes(query)) return false;
|
|
1811
2143
|
if (pathQuery && !lower(node.fileUri).includes(pathQuery)) return false;
|
|
@@ -1822,10 +2154,20 @@ function wireGraph(root) {
|
|
|
1822
2154
|
return true;
|
|
1823
2155
|
}
|
|
1824
2156
|
|
|
2157
|
+
function syncFilesSelect() {
|
|
2158
|
+
if (!fileTreatmentSelect) return;
|
|
2159
|
+
fileTreatmentSelect.value = fileTreatment;
|
|
2160
|
+
const selected = fileTreatmentSelect.selectedOptions?.[0];
|
|
2161
|
+
if (selected?.title) {
|
|
2162
|
+
fileTreatmentSelect.title = selected.title;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
1825
2166
|
function refresh() {
|
|
1826
|
-
const
|
|
2167
|
+
const treated = applyFileTreatment(graph, fileTreatment);
|
|
2168
|
+
const visibleNodes = (treated.nodes || []).filter(matches);
|
|
1827
2169
|
const visibleIds = new Set(visibleNodes.map(node => node.id));
|
|
1828
|
-
const visibleEdges = (
|
|
2170
|
+
const visibleEdges = (treated.edges || []).filter(edge => {
|
|
1829
2171
|
if (!visibleIds.has(edge.sourceId) || !visibleIds.has(edge.targetId)) return false;
|
|
1830
2172
|
if (!includeWildcardRefs && edge.kind === 'wildcard_reference') return false;
|
|
1831
2173
|
return true;
|
|
@@ -1849,6 +2191,11 @@ function wireGraph(root) {
|
|
|
1849
2191
|
toggleIdeasets.textContent = hideIdeasets ? 'Show ideasets' : 'Hide ideasets';
|
|
1850
2192
|
refresh();
|
|
1851
2193
|
});
|
|
2194
|
+
fileTreatmentSelect?.addEventListener('change', () => {
|
|
2195
|
+
fileTreatment = fileTreatmentSelect.value || 'invisible';
|
|
2196
|
+
syncFilesSelect();
|
|
2197
|
+
refresh();
|
|
2198
|
+
});
|
|
1852
2199
|
toggleWildcard?.addEventListener('click', () => {
|
|
1853
2200
|
includeWildcardRefs = !includeWildcardRefs;
|
|
1854
2201
|
toggleWildcard.classList.toggle('is-active', includeWildcardRefs);
|
|
@@ -1866,6 +2213,10 @@ function wireGraph(root) {
|
|
|
1866
2213
|
togglePhysics.classList.toggle('is-active', livePhysics);
|
|
1867
2214
|
togglePhysics.setAttribute('aria-pressed', livePhysics ? 'true' : 'false');
|
|
1868
2215
|
if (livePhysics) {
|
|
2216
|
+
cancelSettle();
|
|
2217
|
+
if (statusText) {
|
|
2218
|
+
statusText.textContent = \`\${simNodes.length} nodes, \${simEdges.length} edges\`;
|
|
2219
|
+
}
|
|
1869
2220
|
wake();
|
|
1870
2221
|
} else {
|
|
1871
2222
|
stopLoop();
|
|
@@ -1885,6 +2236,7 @@ function wireGraph(root) {
|
|
|
1885
2236
|
tagFilter.clear();
|
|
1886
2237
|
hideExternal = false;
|
|
1887
2238
|
hideIdeasets = false;
|
|
2239
|
+
fileTreatment = 'linked';
|
|
1888
2240
|
includeWildcardRefs = true;
|
|
1889
2241
|
labelMode = 'auto';
|
|
1890
2242
|
toggleExternal?.classList.remove('is-active');
|
|
@@ -1892,6 +2244,7 @@ function wireGraph(root) {
|
|
|
1892
2244
|
toggleWildcard?.classList.add('is-active');
|
|
1893
2245
|
toggleWildcard?.setAttribute('aria-pressed', 'true');
|
|
1894
2246
|
syncLabelsButton();
|
|
2247
|
+
syncFilesSelect();
|
|
1895
2248
|
if (toggleExternal) toggleExternal.textContent = 'Hide external';
|
|
1896
2249
|
if (toggleIdeasets) toggleIdeasets.textContent = 'Hide ideasets';
|
|
1897
2250
|
positions.clear();
|
|
@@ -1901,8 +2254,22 @@ function wireGraph(root) {
|
|
|
1901
2254
|
refresh();
|
|
1902
2255
|
});
|
|
1903
2256
|
syncLabelsButton();
|
|
1904
|
-
|
|
2257
|
+
syncFilesSelect();
|
|
2258
|
+
// Defer first mount so page chrome paints before JSON/layout work.
|
|
2259
|
+
requestAnimationFrame(() => {
|
|
2260
|
+
refresh();
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
function scheduleBackground(task) {
|
|
2266
|
+
if (typeof requestIdleCallback === 'function') {
|
|
2267
|
+
requestIdleCallback(() => task(), { timeout: 250 });
|
|
2268
|
+
return;
|
|
1905
2269
|
}
|
|
2270
|
+
requestAnimationFrame(() => {
|
|
2271
|
+
setTimeout(task, 0);
|
|
2272
|
+
});
|
|
1906
2273
|
}
|
|
1907
2274
|
|
|
1908
2275
|
function boot() {
|
|
@@ -1910,8 +2277,9 @@ function boot() {
|
|
|
1910
2277
|
// Interactive Status/Tags triggers before heavy table/graph init.
|
|
1911
2278
|
enhanceScdPlaceholders(root);
|
|
1912
2279
|
wireTables(root);
|
|
1913
|
-
wireGraph(root);
|
|
1914
2280
|
void wireGlobalSearch(root);
|
|
2281
|
+
// Graph boot + settle run in the background so large exports stay responsive.
|
|
2282
|
+
scheduleBackground(() => wireGraph(root));
|
|
1915
2283
|
}
|
|
1916
2284
|
|
|
1917
2285
|
if (document.readyState === 'loading') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-export-assets.js","sourceRoot":"","sources":["../../src/export/html-export-assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAE9E;;;;;;GAMG;AACH,SAAS,sBAAsB;IAC3B,OAAO,2BAA2B,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"html-export-assets.js","sourceRoot":"","sources":["../../src/export/html-export-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -466,6 +466,14 @@ export function renderGraphPage(snapshot) {
|
|
|
466
466
|
${renderMultiFilterSelect('tag', tags, 'Filter by tag')}
|
|
467
467
|
<button type="button" class="graph-action" data-graph-toggle-external="true">Hide external</button>
|
|
468
468
|
<button type="button" class="graph-action" data-graph-toggle-ideasets="true">Hide ideasets</button>
|
|
469
|
+
<label class="graph-file-treatment">
|
|
470
|
+
<span class="visually-hidden">File treatment</span>
|
|
471
|
+
<select class="graph-action graph-select" data-graph-file-treatment aria-label="Hosting .rq file treatment" title="How hosting .rq files appear in the graph">
|
|
472
|
+
<option value="invisible" title="Do not show hosting .rq files — ideas float freely.">Files: hidden</option>
|
|
473
|
+
<option value="compound" title="Draw each hosting .rq file as a container. Drag the box; click the title to open the file.">Files: compound</option>
|
|
474
|
+
<option value="linked" selected title="Show each hosting .rq file as a linked ideaset node. Click the node to open the file.">Files: linked</option>
|
|
475
|
+
</select>
|
|
476
|
+
</label>
|
|
469
477
|
<button type="button" class="graph-action is-active" data-graph-toggle-wildcard aria-pressed="true" title="Show edges expanded from path+idea wildcard references">Wildcard refs</button>
|
|
470
478
|
<button type="button" class="graph-action is-active" data-graph-toggle-labels data-label-mode="auto" aria-pressed="mixed">Labels: auto</button>
|
|
471
479
|
<button type="button" class="graph-action" data-graph-toggle-physics aria-pressed="false">Live physics</button>
|
|
@@ -1022,6 +1030,13 @@ function enrichGraphUrls(snapshot, graph, _currentPath) {
|
|
|
1022
1030
|
else {
|
|
1023
1031
|
delete node.attributeKeys;
|
|
1024
1032
|
}
|
|
1033
|
+
const hostFile = resolveExportFilePage(snapshot, { fileUri: idea.fileUri });
|
|
1034
|
+
if (hostFile && filePageEnabled(snapshot, hostFile.kind)) {
|
|
1035
|
+
node.hostFilePageUrl = hostFile.page.url;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
delete node.hostFilePageUrl;
|
|
1039
|
+
}
|
|
1025
1040
|
}
|
|
1026
1041
|
else {
|
|
1027
1042
|
const fileTarget = resolveExportFilePage(snapshot, {
|