archview 0.2.3__tar.gz → 0.2.4__tar.gz
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.
- {archview-0.2.3 → archview-0.2.4}/PKG-INFO +1 -1
- {archview-0.2.3 → archview-0.2.4}/archview/graph.py +7 -5
- {archview-0.2.3 → archview-0.2.4}/archview/static/live.html +119 -4
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/PKG-INFO +1 -1
- {archview-0.2.3 → archview-0.2.4}/pyproject.toml +1 -1
- {archview-0.2.3 → archview-0.2.4}/LICENSE +0 -0
- {archview-0.2.3 → archview-0.2.4}/README.md +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/__init__.py +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/annotations.py +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/cli.py +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/diff.py +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/server.py +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/static/cytoscape-dagre.js +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/static/cytoscape.min.js +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview/static/dagre.min.js +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/SOURCES.txt +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/dependency_links.txt +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/entry_points.txt +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/requires.txt +0 -0
- {archview-0.2.3 → archview-0.2.4}/archview.egg-info/top_level.txt +0 -0
- {archview-0.2.3 → archview-0.2.4}/setup.cfg +0 -0
- {archview-0.2.3 → archview-0.2.4}/tests/test_graph.py +0 -0
|
@@ -144,6 +144,8 @@ def _build_module_index(
|
|
|
144
144
|
mod = rel.replace("/", ".").removesuffix(ext)
|
|
145
145
|
if mod.endswith(".__init__"):
|
|
146
146
|
mod = mod[:-9]
|
|
147
|
+
if not mod:
|
|
148
|
+
continue
|
|
147
149
|
modules[mod] = full
|
|
148
150
|
module_rel[mod] = rel
|
|
149
151
|
return modules, module_rel
|
|
@@ -158,7 +160,7 @@ def _parse_modules(modules: dict[str, Path]):
|
|
|
158
160
|
|
|
159
161
|
for mod, path in modules.items():
|
|
160
162
|
try:
|
|
161
|
-
tree = ast.parse(path.read_text())
|
|
163
|
+
tree = ast.parse(path.read_text(), filename=str(path))
|
|
162
164
|
parsed[mod] = tree
|
|
163
165
|
except SyntaxError as e:
|
|
164
166
|
parse_errors[mod] = f"SyntaxError: {e.msg} (line {e.lineno})"
|
|
@@ -515,9 +517,7 @@ def _build_elements(
|
|
|
515
517
|
for node in all_nodes:
|
|
516
518
|
rel = module_rel.get(node, "")
|
|
517
519
|
raw_name = Path(rel).name if rel else node.split(".")[-1]
|
|
518
|
-
filename = (
|
|
519
|
-
node.split(".")[-1] + ".py" if raw_name == "__init__.py" else raw_name
|
|
520
|
-
)
|
|
520
|
+
filename = node.split(".")[-1] if raw_name == "__init__.py" else raw_name
|
|
521
521
|
if node in parse_errors:
|
|
522
522
|
bg, fg = NODE_COLORS["error"]
|
|
523
523
|
label = "\u26a0 " + filename
|
|
@@ -626,7 +626,9 @@ def generate_graph_json(project_dir: Path, ignore_file: Path | None) -> list[dic
|
|
|
626
626
|
parts = node.split(".")
|
|
627
627
|
if len(parts) > 1:
|
|
628
628
|
candidate = ".".join(parts[:-1])
|
|
629
|
-
|
|
629
|
+
# leading-dot hidden files (e.g. .eslintrc.json) produce an empty
|
|
630
|
+
# candidate; skip to avoid emitting an element with id ""
|
|
631
|
+
if candidate and candidate not in all_nodes_set:
|
|
630
632
|
folder_ids.add(candidate)
|
|
631
633
|
|
|
632
634
|
all_containers = all_nodes_set | folder_ids
|
|
@@ -358,6 +358,7 @@
|
|
|
358
358
|
<span id="status">Loading…</span>
|
|
359
359
|
<button id="btn-fit">Fit</button>
|
|
360
360
|
<button id="btn-layout">Reset layout</button>
|
|
361
|
+
<button id="btn-folders">Collapse all</button>
|
|
361
362
|
<button id="btn-png">PNG</button>
|
|
362
363
|
<button id="btn-save" class="primary">Save</button>
|
|
363
364
|
</div>
|
|
@@ -601,7 +602,10 @@ const cy = cytoscape({
|
|
|
601
602
|
});
|
|
602
603
|
|
|
603
604
|
cy.on('dragfree', 'node', evt => {
|
|
604
|
-
|
|
605
|
+
const id = evt.target.id();
|
|
606
|
+
// expanded compounds have positions derived from children — don't persist
|
|
607
|
+
if (compoundNodes.has(id) && expandedFolders.has(id)) return;
|
|
608
|
+
userPositions[id] = { ...evt.target.position() };
|
|
605
609
|
});
|
|
606
610
|
|
|
607
611
|
// ── Folder collapse / expand ──────────────────────────────────────────────
|
|
@@ -731,6 +735,8 @@ function toggleFolder(id) {
|
|
|
731
735
|
userPositions[childId].x += dx;
|
|
732
736
|
userPositions[childId].y += dy;
|
|
733
737
|
}
|
|
738
|
+
const sp = savedPositions.get(childId);
|
|
739
|
+
if (sp) { sp.x += dx; sp.y += dy; }
|
|
734
740
|
}
|
|
735
741
|
}
|
|
736
742
|
collapsePositions.delete(id);
|
|
@@ -741,8 +747,70 @@ function toggleFolder(id) {
|
|
|
741
747
|
}
|
|
742
748
|
updateFolderLabels();
|
|
743
749
|
applyCollapse();
|
|
750
|
+
if (expandedFolders.has(id)) applyGridInsideFolders(id);
|
|
744
751
|
cy.nodes(':hidden').forEach(n => selectedNodes.delete(n.id()));
|
|
745
752
|
applyFocus();
|
|
753
|
+
updateFolderButtonLabel();
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function updateFolderButtonLabel() {
|
|
757
|
+
const btn = document.getElementById('btn-folders');
|
|
758
|
+
if (!btn) return;
|
|
759
|
+
if (compoundNodes.size === 0) {
|
|
760
|
+
btn.style.display = 'none';
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
btn.style.display = '';
|
|
764
|
+
const anyExpanded = [...compoundNodes].some(id => expandedFolders.has(id));
|
|
765
|
+
btn.textContent = anyExpanded ? 'Collapse all' : 'Expand all';
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function applyGridInsideFolders(targetId = null) {
|
|
769
|
+
const STEP_X = 170;
|
|
770
|
+
const STEP_Y = 60;
|
|
771
|
+
let ids;
|
|
772
|
+
if (targetId) {
|
|
773
|
+
// include target plus any expanded compound descendants
|
|
774
|
+
ids = [targetId];
|
|
775
|
+
const stack = [targetId];
|
|
776
|
+
while (stack.length) {
|
|
777
|
+
const cur = stack.pop();
|
|
778
|
+
const node = cy.getElementById(cur);
|
|
779
|
+
if (!node.length) continue;
|
|
780
|
+
node.children().forEach(child => {
|
|
781
|
+
const cid = child.id();
|
|
782
|
+
if (compoundNodes.has(cid) && expandedFolders.has(cid)) {
|
|
783
|
+
ids.push(cid);
|
|
784
|
+
stack.push(cid);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
ids = [...compoundNodes];
|
|
790
|
+
}
|
|
791
|
+
ids.forEach(folderId => {
|
|
792
|
+
if (!expandedFolders.has(folderId)) return;
|
|
793
|
+
const folder = cy.getElementById(folderId);
|
|
794
|
+
if (!folder.length) return;
|
|
795
|
+
const children = folder.children().filter(n => n.visible());
|
|
796
|
+
const count = children.length;
|
|
797
|
+
if (count <= 1) return;
|
|
798
|
+
if (children.some(c => userPositions[c.id()])) return;
|
|
799
|
+
const cols = Math.ceil(Math.sqrt(count));
|
|
800
|
+
const rows = Math.ceil(count / cols);
|
|
801
|
+
let sumX = 0, sumY = 0;
|
|
802
|
+
children.forEach(c => { sumX += c.position().x; sumY += c.position().y; });
|
|
803
|
+
const cx = sumX / count;
|
|
804
|
+
const cy_ = sumY / count;
|
|
805
|
+
children.forEach((child, i) => {
|
|
806
|
+
const col = i % cols;
|
|
807
|
+
const row = Math.floor(i / cols);
|
|
808
|
+
child.position({
|
|
809
|
+
x: cx + (col - (cols - 1) / 2) * STEP_X,
|
|
810
|
+
y: cy_ + (row - (rows - 1) / 2) * STEP_Y,
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
});
|
|
746
814
|
}
|
|
747
815
|
|
|
748
816
|
// ── Node focus ───────────────────────────────────────────────────────────
|
|
@@ -897,9 +965,9 @@ function runLayout(fit, newNodeIds = new Set()) {
|
|
|
897
965
|
cy.layout({
|
|
898
966
|
name: 'dagre',
|
|
899
967
|
rankDir: 'LR',
|
|
900
|
-
nodeSep:
|
|
901
|
-
rankSep:
|
|
902
|
-
edgeSep:
|
|
968
|
+
nodeSep: 10,
|
|
969
|
+
rankSep: 50,
|
|
970
|
+
edgeSep: 8,
|
|
903
971
|
compound: true,
|
|
904
972
|
animate: !firstLoad,
|
|
905
973
|
animationDuration: 400,
|
|
@@ -912,6 +980,7 @@ function runLayout(fit, newNodeIds = new Set()) {
|
|
|
912
980
|
}
|
|
913
981
|
}).run();
|
|
914
982
|
applyCollapse();
|
|
983
|
+
applyGridInsideFolders();
|
|
915
984
|
applyFocus();
|
|
916
985
|
}
|
|
917
986
|
|
|
@@ -979,6 +1048,7 @@ function reconcileGraph({ nodeIds, edgeIds, nodeMap, edgeMap }) {
|
|
|
979
1048
|
function trackCompoundNodes(elements) {
|
|
980
1049
|
compoundNodes.clear();
|
|
981
1050
|
for (const el of elements) {
|
|
1051
|
+
if (el.data.source) continue; // edges
|
|
982
1052
|
if (el.data.parent) compoundNodes.add(el.data.parent);
|
|
983
1053
|
if (el.data.is_folder) compoundNodes.add(el.data.id);
|
|
984
1054
|
}
|
|
@@ -1019,6 +1089,7 @@ async function refresh() {
|
|
|
1019
1089
|
runLayout(firstLoad, new Set(addedNodes));
|
|
1020
1090
|
if (firstLoad) updateFolderLabels();
|
|
1021
1091
|
firstLoad = false;
|
|
1092
|
+
updateFolderButtonLabel();
|
|
1022
1093
|
} else {
|
|
1023
1094
|
applyCollapse();
|
|
1024
1095
|
applyFocus();
|
|
@@ -1056,6 +1127,50 @@ document.getElementById('btn-layout').addEventListener('click', () => {
|
|
|
1056
1127
|
runLayout(true, new Set(cy.nodes().map(n => n.id())));
|
|
1057
1128
|
});
|
|
1058
1129
|
|
|
1130
|
+
document.getElementById('btn-folders').addEventListener('click', () => {
|
|
1131
|
+
const anyExpanded = [...compoundNodes].some(id => expandedFolders.has(id));
|
|
1132
|
+
if (anyExpanded) {
|
|
1133
|
+
compoundNodes.forEach(id => {
|
|
1134
|
+
if (expandedFolders.has(id)) {
|
|
1135
|
+
const node = cy.getElementById(id);
|
|
1136
|
+
if (node.length) collapsePositions.set(id, { ...node.position() });
|
|
1137
|
+
expandedFolders.delete(id);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
} else {
|
|
1141
|
+
compoundNodes.forEach(id => {
|
|
1142
|
+
if (!expandedFolders.has(id)) {
|
|
1143
|
+
const savedPos = collapsePositions.get(id);
|
|
1144
|
+
const node = cy.getElementById(id);
|
|
1145
|
+
if (node.length && savedPos) {
|
|
1146
|
+
const cur = node.position();
|
|
1147
|
+
const dx = cur.x - savedPos.x;
|
|
1148
|
+
const dy = cur.y - savedPos.y;
|
|
1149
|
+
if (dx !== 0 || dy !== 0) {
|
|
1150
|
+
for (const childId of getDescendants(id)) {
|
|
1151
|
+
if (userPositions[childId]) {
|
|
1152
|
+
userPositions[childId].x += dx;
|
|
1153
|
+
userPositions[childId].y += dy;
|
|
1154
|
+
}
|
|
1155
|
+
const sp = savedPositions.get(childId);
|
|
1156
|
+
if (sp) { sp.x += dx; sp.y += dy; }
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
collapsePositions.delete(id);
|
|
1160
|
+
}
|
|
1161
|
+
delete userPositions[id];
|
|
1162
|
+
expandedFolders.add(id);
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
updateFolderLabels();
|
|
1167
|
+
applyCollapse();
|
|
1168
|
+
applyGridInsideFolders();
|
|
1169
|
+
cy.nodes(':hidden').forEach(n => selectedNodes.delete(n.id()));
|
|
1170
|
+
applyFocus();
|
|
1171
|
+
updateFolderButtonLabel();
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1059
1174
|
document.getElementById('btn-fit').addEventListener('click', () => cy.fit(48));
|
|
1060
1175
|
|
|
1061
1176
|
document.getElementById('btn-png').addEventListener('click', () => {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|