Skip to content

Commit 4d7f26f

Browse files
GiggleLiuclaude
andcommitted
docs: add interactive JSON tree viewer
- JSON blocks in <details> render as collapsible trees - Toggle between tree view and raw JSON - Styled to match mdBook dark theme 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6a5e32d commit 4d7f26f

4 files changed

Lines changed: 206 additions & 9 deletions

File tree

book.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ src = "docs/src"
99
default-theme = "navy"
1010
git-repository-url = "https://github.com/CodingThrust/problem-reductions"
1111
edit-url-template = "https://github.com/CodingThrust/problem-reductions/edit/main/{path}"
12-
additional-css = ["docs/src/static/theme-images.css"]
13-
additional-js = []
12+
additional-css = ["docs/src/static/theme-images.css", "docs/src/static/json-viewer.css"]
13+
additional-js = ["docs/src/static/json-viewer.js"]
1414
no-section-label = false
1515

1616
[output.html.fold]

docs/src/arch.md

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,37 +150,72 @@ Variant IDs follow the pattern `ProblemName[/GraphType][/Weighted]`:
150150
| `MaximumIndependentSet/Weighted` | Weighted objective |
151151
| `MaximumIndependentSet/GridGraph/Weighted` | Both |
152152

153-
The graph is exported to `reduction_graph.json` for visualization and documentation:
153+
The graph is exported to [`reduction_graph.json`](reductions/reduction_graph.json) for visualization and documentation:
154+
155+
<details>
156+
<summary>Schema</summary>
154157

155158
```json
156159
{
157160
"nodes": [
158-
{"name": "Satisfiability", "variant": {}, "category": "satisfiability", "doc_path": "..."},
159-
{"name": "MaximumIndependentSet", "variant": {"graph": "GridGraph"}, "category": "graph", "doc_path": "..."}
161+
{
162+
"name": "Satisfiability",
163+
"variant": {},
164+
"category": "satisfiability",
165+
"doc_path": "..."
166+
},
167+
{
168+
"name": "MaximumIndependentSet",
169+
"variant": {"graph": "GridGraph"},
170+
"category": "graph",
171+
"doc_path": "..."
172+
}
160173
],
161174
"edges": [
162-
{"source": {"name": "Satisfiability", "variant": {}}, "target": {"name": "MaximumIndependentSet", "variant": {}}}
175+
{
176+
"source": {"name": "Satisfiability", "variant": {}},
177+
"target": {"name": "MaximumIndependentSet", "variant": {}}
178+
}
163179
]
164180
}
165181
```
166182

183+
</details>
184+
167185
Problem schemas (`problem_schemas.json`) describe each problem's structure:
168186

187+
<details>
188+
<summary><code>problem_schemas.json</code> schema</summary>
189+
169190
```json
170191
[
171192
{
172193
"name": "Satisfiability",
173194
"category": "satisfiability",
174195
"description": "Find satisfying assignment for CNF formula",
175196
"fields": [
176-
{"name": "num_vars", "type_name": "usize", "description": "Number of Boolean variables"},
177-
{"name": "clauses", "type_name": "Vec<CNFClause>", "description": "Clauses in conjunctive normal form"},
178-
{"name": "weights", "type_name": "Vec<W>", "description": "Clause weights for MAX-SAT"}
197+
{
198+
"name": "num_vars",
199+
"type_name": "usize",
200+
"description": "Number of Boolean variables"
201+
},
202+
{
203+
"name": "clauses",
204+
"type_name": "Vec<CNFClause>",
205+
"description": "Clauses in conjunctive normal form"
206+
},
207+
{
208+
"name": "weights",
209+
"type_name": "Vec<W>",
210+
"description": "Clause weights for MAX-SAT"
211+
}
179212
]
180213
}
181214
]
182215
```
183216

217+
</details>
218+
184219
Use the interactive diagram in the [mdBook documentation](https://codingthrust.github.io/problem-reductions/) to explore available reductions.
185220

186221
### Implementation

docs/src/static/json-viewer.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* JSON Tree Viewer Styles */
2+
3+
.json-tree {
4+
font-family: 'Source Code Pro', monospace;
5+
font-size: 0.9em;
6+
line-height: 1.5;
7+
padding: 0.5em;
8+
background: var(--code-bg);
9+
border-radius: 4px;
10+
overflow-x: auto;
11+
}
12+
13+
.json-item {
14+
padding: 2px 0;
15+
}
16+
17+
.json-toggle {
18+
cursor: pointer;
19+
color: var(--links);
20+
user-select: none;
21+
}
22+
23+
.json-toggle:hover {
24+
text-decoration: underline;
25+
}
26+
27+
.collapsed > .json-content {
28+
display: none;
29+
}
30+
31+
.collapsed > .json-toggle::before {
32+
content: '+ ';
33+
}
34+
35+
.json-key {
36+
color: #9cdcfe;
37+
}
38+
39+
.json-string {
40+
color: #ce9178;
41+
}
42+
43+
.json-number {
44+
color: #b5cea8;
45+
}
46+
47+
.json-boolean {
48+
color: #569cd6;
49+
}
50+
51+
.json-null {
52+
color: #569cd6;
53+
}
54+
55+
.json-index {
56+
color: #888;
57+
}
58+
59+
.json-empty {
60+
color: #888;
61+
}
62+
63+
.json-raw-toggle {
64+
font-size: 0.8em;
65+
padding: 2px 8px;
66+
margin-bottom: 8px;
67+
cursor: pointer;
68+
background: var(--quote-bg);
69+
border: 1px solid var(--quote-border);
70+
border-radius: 4px;
71+
color: var(--fg);
72+
}
73+
74+
.json-raw-toggle:hover {
75+
background: var(--table-alternate-bg);
76+
}

docs/src/static/json-viewer.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Simple JSON tree viewer for mdBook
2+
// Converts JSON code blocks into interactive collapsible trees
3+
4+
(function() {
5+
function createJsonTree(obj, indent = 0) {
6+
const type = Array.isArray(obj) ? 'array' : typeof obj;
7+
8+
if (type === 'object' && obj !== null) {
9+
const entries = Object.entries(obj);
10+
if (entries.length === 0) return '<span class="json-empty">{}</span>';
11+
12+
const items = entries.map(([key, value]) => {
13+
const valueHtml = createJsonTree(value, indent + 1);
14+
return `<div class="json-item" style="margin-left: ${indent * 16}px">
15+
<span class="json-key">"${key}"</span>: ${valueHtml}
16+
</div>`;
17+
}).join('');
18+
19+
return `<span class="json-toggle" onclick="this.parentElement.classList.toggle('collapsed')">{...}</span>
20+
<div class="json-content">${items}</div>`;
21+
} else if (type === 'array') {
22+
if (obj.length === 0) return '<span class="json-empty">[]</span>';
23+
24+
const items = obj.map((value, i) => {
25+
const valueHtml = createJsonTree(value, indent + 1);
26+
return `<div class="json-item" style="margin-left: ${indent * 16}px">
27+
<span class="json-index">${i}</span>: ${valueHtml}
28+
</div>`;
29+
}).join('');
30+
31+
return `<span class="json-toggle" onclick="this.parentElement.classList.toggle('collapsed')">[${obj.length}]</span>
32+
<div class="json-content">${items}</div>`;
33+
} else if (type === 'string') {
34+
return `<span class="json-string">"${obj}"</span>`;
35+
} else if (type === 'number') {
36+
return `<span class="json-number">${obj}</span>`;
37+
} else if (type === 'boolean') {
38+
return `<span class="json-boolean">${obj}</span>`;
39+
} else {
40+
return `<span class="json-null">null</span>`;
41+
}
42+
}
43+
44+
function initJsonViewer() {
45+
// Find all JSON code blocks inside <details> elements
46+
document.querySelectorAll('details pre code.language-json').forEach(block => {
47+
try {
48+
const json = JSON.parse(block.textContent);
49+
const container = document.createElement('div');
50+
container.className = 'json-tree';
51+
container.innerHTML = createJsonTree(json);
52+
53+
// Replace the pre element with the tree
54+
const pre = block.parentElement;
55+
pre.parentElement.insertBefore(container, pre);
56+
pre.style.display = 'none';
57+
58+
// Add toggle to show raw JSON
59+
const toggle = document.createElement('button');
60+
toggle.className = 'json-raw-toggle';
61+
toggle.textContent = 'Show raw JSON';
62+
toggle.onclick = () => {
63+
if (pre.style.display === 'none') {
64+
pre.style.display = 'block';
65+
container.style.display = 'none';
66+
toggle.textContent = 'Show tree view';
67+
} else {
68+
pre.style.display = 'none';
69+
container.style.display = 'block';
70+
toggle.textContent = 'Show raw JSON';
71+
}
72+
};
73+
container.parentElement.insertBefore(toggle, container);
74+
} catch (e) {
75+
// Not valid JSON, leave as-is
76+
}
77+
});
78+
}
79+
80+
// Run after DOM is ready
81+
if (document.readyState === 'loading') {
82+
document.addEventListener('DOMContentLoaded', initJsonViewer);
83+
} else {
84+
initJsonViewer();
85+
}
86+
})();

0 commit comments

Comments
 (0)