Skip to content

Commit 6820fa1

Browse files
author
Vic-Nas
committed
Add GitHub Action to generate data.json
1 parent 78ce5f5 commit 6820fa1

File tree

2 files changed

+194
-95
lines changed

2 files changed

+194
-95
lines changed

.github/workflows/build-index.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Build Repository Data
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Generate data.json
15+
run: |
16+
python3 << 'EOF'
17+
import os
18+
import json
19+
from pathlib import Path
20+
21+
def has_files(path):
22+
"""Check if directory contains problem files"""
23+
for item in path.iterdir():
24+
if item.is_file():
25+
name = item.name
26+
if name.endswith('.vn.py') or name.endswith('.vn.png') or name.endswith('.html'):
27+
return True
28+
return False
29+
30+
def count_items(path):
31+
"""Recursively count problem folders"""
32+
if has_files(path):
33+
return 1
34+
35+
count = 0
36+
for item in path.iterdir():
37+
if item.is_dir() and not item.name.startswith('.'):
38+
count += count_items(item)
39+
return count
40+
41+
def build_tree(path, prefix=''):
42+
"""Build complete directory tree for navigation"""
43+
items = []
44+
45+
for item in sorted(path.iterdir()):
46+
if item.is_dir() and not item.name.startswith('.'):
47+
rel_path = str(item.relative_to(Path('.')))
48+
49+
node = {
50+
'name': item.name,
51+
'path': rel_path,
52+
'type': 'dir',
53+
'has_files': has_files(item)
54+
}
55+
56+
# Recurse into subdirectories
57+
children = build_tree(item, rel_path + '/')
58+
if children:
59+
node['children'] = children
60+
61+
items.append(node)
62+
63+
return items
64+
65+
# Build platform data
66+
platforms = []
67+
root = Path('.')
68+
69+
excluded = {'utils', '.git', '.github', '.vscode'}
70+
71+
for platform_dir in sorted(root.iterdir()):
72+
if platform_dir.is_dir() and platform_dir.name not in excluded and not platform_dir.name.startswith('.'):
73+
platform = {
74+
'name': platform_dir.name,
75+
'count': count_items(platform_dir),
76+
'image': f'{platform_dir.name}/platform.png' if (platform_dir / 'platform.png').exists() else None,
77+
'tree': build_tree(platform_dir)
78+
}
79+
platforms.append(platform)
80+
81+
# Write data.json
82+
data = {
83+
'platforms': platforms,
84+
'generated_at': __import__('datetime').datetime.now().isoformat()
85+
}
86+
87+
with open('data.json', 'w') as f:
88+
json.dump(data, f, indent=2)
89+
90+
print(f"✓ Generated data for {len(platforms)} platforms")
91+
for p in platforms:
92+
print(f" - {p['name']}: {p['count']} items")
93+
EOF
94+
95+
- name: Commit and push if changed
96+
run: |
97+
git config user.name 'github-actions[bot]'
98+
git config user.email 'github-actions[bot]@users.noreply.github.com'
99+
git add data.json
100+
101+
if git diff --staged --quiet; then
102+
echo "No changes to data.json"
103+
else
104+
git commit -m "🤖 Auto-generate data.json [skip ci]"
105+
git push
106+
fi

script.js

Lines changed: 88 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ console.log('Script loaded');
33
// State
44
const state = {
55
platforms: [],
6-
folderCache: {},
6+
treeData: {},
77
currentView: 'loading',
88
currentPath: [],
99
currentItem: null
@@ -15,101 +15,106 @@ function getRepoPath() {
1515
return parts[1] && parts[2] ? `${parts[1]}/${parts[2]}` : 'Vic-Nas/PythonSolutions';
1616
}
1717

18-
// Fetch folder contents with in-memory caching
19-
async function fetchFolderContents(path) {
20-
// Return cached data if available
21-
if (state.folderCache[path]) {
22-
return state.folderCache[path];
23-
}
18+
// Load platforms from pre-generated data.json (no API calls!)
19+
async function loadPlatforms() {
20+
console.log('Loading platforms from data.json...');
2421

2522
try {
26-
const res = await fetch(`https://api.github.com/repos/${getRepoPath()}/contents/${path}`);
27-
23+
const res = await fetch('data.json');
2824
if (!res.ok) {
29-
console.error(`Failed to fetch ${path}: ${res.status} ${res.statusText}`);
30-
return null;
25+
console.error('Failed to load data.json');
26+
return;
3127
}
3228

33-
const items = await res.json();
34-
state.folderCache[path] = items;
35-
return items;
29+
const data = await res.json();
30+
state.platforms = data.platforms || [];
31+
32+
// Build tree lookup for fast navigation
33+
state.platforms.forEach(platform => {
34+
state.treeData[platform.name] = platform.tree || [];
35+
});
36+
37+
console.log('Loaded platforms:', state.platforms.map(p => p.name));
3638
} catch (err) {
37-
console.error(`Error fetching ${path}:`, err);
38-
return null;
39+
console.error('Error loading data.json:', err);
3940
}
4041
}
4142

42-
// Check if folder contains files (is a "problem")
43-
function hasFiles(items) {
44-
return items.some(item =>
45-
item.type === 'file' &&
46-
(item.name.endsWith('.vn.py') ||
47-
item.name.endsWith('.vn.png') ||
48-
item.name.endsWith('.html'))
49-
);
43+
// Find node in tree by path
44+
function findInTree(tree, pathParts, startIndex = 0) {
45+
if (startIndex >= pathParts.length) return null;
46+
47+
const currentPart = pathParts[startIndex];
48+
const node = tree.find(n => n.name === currentPart);
49+
50+
if (!node) return null;
51+
if (startIndex === pathParts.length - 1) return node;
52+
if (!node.children) return null;
53+
54+
return findInTree(node.children, pathParts, startIndex + 1);
5055
}
5156

52-
// Load all platforms
53-
async function loadPlatforms() {
54-
console.log('Loading platforms...');
55-
const rootItems = await fetchFolderContents('');
56-
if (!rootItems) {
57-
console.error('Failed to load root directory');
58-
return;
59-
}
57+
// Get folder contents from tree data
58+
function getFolderFromTree(pathParts) {
59+
if (pathParts.length === 0) return null;
6060

61-
const platformFolders = rootItems.filter(item =>
62-
item.type === 'dir' &&
63-
!['utils', '.git'].includes(item.name) &&
64-
!item.name.startsWith('.')
65-
);
61+
const platformName = pathParts[0];
62+
const tree = state.treeData[platformName];
6663

67-
state.platforms = [];
64+
if (!tree) return null;
65+
if (pathParts.length === 1) return tree;
6866

69-
for (const folder of platformFolders) {
70-
const platformData = {
71-
name: folder.name,
72-
path: folder.name,
73-
image: null,
74-
count: 0
75-
};
76-
77-
// Check for platform.png
78-
const contents = await fetchFolderContents(folder.name);
79-
if (contents) {
80-
const platformImg = contents.find(f => f.name === 'platform.png');
81-
if (platformImg) {
82-
platformData.image = platformImg.download_url || `${folder.name}/platform.png`;
83-
}
84-
85-
// Count items recursively
86-
platformData.count = await countItems(folder.name);
87-
}
88-
89-
state.platforms.push(platformData);
90-
}
67+
const node = findInTree(tree, pathParts, 1);
68+
return node ? (node.children || []) : null;
69+
}
70+
71+
// Check if path has files (is a problem)
72+
function hasFiles(pathParts) {
73+
if (pathParts.length === 0) return false;
9174

92-
state.platforms.sort((a, b) => a.name.localeCompare(b.name));
93-
console.log('Loaded platforms:', state.platforms);
75+
const platformName = pathParts[0];
76+
const tree = state.treeData[platformName];
77+
78+
if (!tree) return false;
79+
if (pathParts.length === 1) return false;
80+
81+
const node = findInTree(tree, pathParts, 1);
82+
return node ? node.has_files : false;
9483
}
9584

96-
// Recursively count problem items
97-
async function countItems(path) {
98-
const items = await fetchFolderContents(path);
85+
// Count items in path
86+
function countItemsFromTree(pathParts) {
87+
const items = getFolderFromTree(pathParts);
9988
if (!items) return 0;
10089

101-
if (hasFiles(items)) return 1;
102-
103-
const subdirs = items.filter(i => i.type === 'dir');
10490
let count = 0;
10591

106-
for (const dir of subdirs) {
107-
count += await countItems(`${path}/${dir.name}`);
92+
function countRecursive(nodes) {
93+
for (const node of nodes) {
94+
if (node.has_files) {
95+
count++;
96+
} else if (node.children) {
97+
countRecursive(node.children);
98+
}
99+
}
108100
}
109101

102+
countRecursive(items);
110103
return count;
111104
}
112105

106+
// Fetch file contents from GitHub (only when viewing a problem)
107+
async function fetchFileContent(path) {
108+
try {
109+
const res = await fetch(`https://api.github.com/repos/${getRepoPath()}/contents/${path}`);
110+
if (!res.ok) return null;
111+
return await res.json();
112+
} catch (err) {
113+
console.error(`Error fetching ${path}:`, err);
114+
return null;
115+
}
116+
}
117+
113118
// Parse hash to navigate
114119
function parseHash() {
115120
let hash = window.location.hash.slice(1);
@@ -178,21 +183,17 @@ function renderPlatforms() {
178183
});
179184
}
180185

181-
async function renderFolder() {
186+
function renderFolder() {
182187
const view = document.getElementById('folder-view');
183188
view.style.display = 'block';
184189

185-
const pathStr = state.currentPath.join('/');
186-
const items = await fetchFolderContents(pathStr);
190+
const items = getFolderFromTree(state.currentPath);
187191

188192
if (!items) {
189193
view.innerHTML = '<p>Error loading folder</p>';
190194
return;
191195
}
192196

193-
// Get subdirectories only
194-
const subdirs = items.filter(i => i.type === 'dir');
195-
196197
// Set title
197198
const titleParts = state.currentPath.map(capitalize);
198199
document.getElementById('folder-title').textContent = titleParts.join(' / ');
@@ -205,22 +206,22 @@ async function renderFolder() {
205206
const container = document.getElementById('folder-cards');
206207
container.innerHTML = '';
207208

208-
for (const dir of subdirs) {
209+
items.forEach(item => {
209210
const card = document.createElement('div');
210211
card.className = 'folder-card';
211212

212-
const fullPath = [...state.currentPath, dir.name];
213-
const count = await countItems(fullPath.join('/'));
213+
const fullPath = [...state.currentPath, item.name];
214+
const count = countItemsFromTree(fullPath);
214215

215216
card.innerHTML = `
216-
<div class="folder-card-title">${dir.name}</div>
217+
<div class="folder-card-title">${item.name}</div>
217218
<div class="folder-card-path">${fullPath.join('/')}</div>
218219
<div class="folder-card-count">${count} item${count !== 1 ? 's' : ''}</div>
219220
`;
220221

221222
card.onclick = () => navigateTo(fullPath);
222223
container.appendChild(card);
223-
}
224+
});
224225
}
225226

226227
async function renderProblem() {
@@ -229,7 +230,7 @@ async function renderProblem() {
229230
view.innerHTML = '<div style="text-align: center; padding: 3rem;">Loading...</div>';
230231

231232
const pathStr = state.currentPath.join('/');
232-
const items = await fetchFolderContents(pathStr);
233+
const items = await fetchFileContent(pathStr);
233234

234235
if (!items) {
235236
view.innerHTML = '<p>Error loading problem</p>';
@@ -367,19 +368,11 @@ async function renderProblem() {
367368
}
368369

369370
// Navigation
370-
async function navigateTo(path) {
371+
function navigateTo(path) {
371372
console.log('Navigating to:', path);
372373

373-
const pathStr = path.join('/');
374-
const items = await fetchFolderContents(pathStr);
375-
376-
if (!items) {
377-
console.error('Could not load path:', pathStr);
378-
return;
379-
}
380-
381-
// Check if this folder has files (is a "problem")
382-
if (hasFiles(items)) {
374+
// Check if this folder has files (is a problem)
375+
if (hasFiles(path)) {
383376
window.location.hash = `view/${path.map(encodeURIComponent).join('/')}`;
384377
} else {
385378
window.location.hash = path.map(encodeURIComponent).join('/');
@@ -435,7 +428,7 @@ function getDefaultEmoji(platformName) {
435428
}
436429

437430
// Event listeners
438-
window.addEventListener('hashchange', async () => {
431+
window.addEventListener('hashchange', () => {
439432
console.log('Hash changed');
440433
const parsed = parseHash();
441434
state.currentView = parsed.view;
@@ -459,7 +452,7 @@ window.addEventListener('hashchange', async () => {
459452
// Make goBack globally available
460453
window.goBack = goBack;
461454

462-
// Load platforms
455+
// Load platforms from data.json
463456
await loadPlatforms();
464457

465458
// Parse hash and render

0 commit comments

Comments
 (0)