Skip to content

Commit 981f7ea

Browse files
committed
Added Tutorials
1 parent 74900b0 commit 981f7ea

4 files changed

Lines changed: 462 additions & 84 deletions

File tree

25.2 KB
Binary file not shown.

assets/nav.js

Lines changed: 167 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,167 @@
1-
(function() {
2-
const SITE = 'https://processing-cpp.github.io';
3-
const path = window.location.pathname;
4-
const parts = path.replace(/\/$/, '').split('/').filter(Boolean);
5-
const isRoot = parts.length === 0 || (parts.length === 1 && parts[0] === 'index.html');
6-
// Depth = number of directory levels below site root. A trailing
7-
// "index.html" or "<file>.html" segment doesn't count as a directory level.
8-
const hasTrailingFile = parts.length > 0 && parts[parts.length - 1].endsWith('.html');
9-
const depth = isRoot ? 0 : (hasTrailingFile ? parts.length - 1 : parts.length);
10-
const prefix = depth === 0 ? '/' : '../'.repeat(depth);
11-
12-
function isActive(name) { return path.includes('/' + name); }
13-
14-
function link(href, label, style, activeKey) {
15-
const active = isActive(activeKey || href) ? ' class="active"' : '';
16-
const s = style ? ` style="${style}"` : '';
17-
return `<a href="${prefix}${href}"${active}${s}>${label}</a>`;
18-
}
19-
20-
const nav = document.getElementById('site-nav');
21-
if (nav) {
22-
nav.innerHTML = `
23-
<a href="${SITE}" class="nav-logo">
24-
<img src="${prefix}assets/cpp-logo.png" alt="Processing for C++">
25-
<div class="nav-title">
26-
<span class="nav-title-top">Processing</span>
27-
<span class="nav-title-bottom">C++</span>
28-
</div>
29-
</a>
30-
<button class="hamburger" onclick="
31-
var s = document.querySelector('.sidebar-outer, .sidebar');
32-
if(s) s.classList.toggle('open');
33-
">☰</button>
34-
<a href="${prefix}error/index.html" id="nav-errors-link"${isActive('error') ? ' class="active"' : ''}>Errors</a>
35-
`;
36-
}
37-
38-
const sidebar = document.getElementById('site-sidebar') || document.querySelector('.sidebar');
39-
if (sidebar) {
40-
sidebar.innerHTML = `
41-
${link('whats-new', "What's New", 'color:#e8b400;font-weight:700;')}
42-
<div style="height:1px;background:#e0e0e0;margin:0.5rem 0;"></div>
43-
${link('libraries', 'Libraries')}
44-
${link('reference', 'Reference')}
45-
${link('examples', 'Examples')}
46-
${link('about', 'About')}
47-
`;
48-
}
49-
50-
if (!document.getElementById('nav-shared-style')) {
51-
const style = document.createElement('style');
52-
style.id = 'nav-shared-style';
53-
style.textContent = `
54-
#site-nav {
55-
border-bottom: 1px solid #e0e0e0;
56-
padding: 0 2rem;
57-
display: flex;
58-
align-items: center;
59-
justify-content: space-between;
60-
height: 60px;
61-
position: sticky;
62-
top: 0;
63-
background: #fff;
64-
z-index: 100;
65-
}
66-
.nav-logo { display: flex; align-items: center; gap: 10px; text-decoration: none; }
67-
.nav-logo img { width: 28px; height: 28px; }
68-
.nav-title { display: flex; flex-direction: column; line-height: 1.15; }
69-
.nav-title-top { font-size: 14px; font-weight: 700; color: #e8b400; }
70-
.nav-title-bottom { font-size: 14px; font-weight: 700; color: #e8b400; }
71-
#nav-errors-link { margin-left: auto; font-size: 14px; font-weight: 700; color: #e8b400; text-decoration: none; }
72-
#nav-errors-link:hover { color: #c99700; }
73-
.hamburger { background: none; border: none; cursor: pointer; font-size: 22px; padding: 4px 8px; display: none; margin-left: 0.5rem; }
74-
@media (max-width: 768px) { .hamburger { display: block; } }
75-
`;
76-
document.head.appendChild(style);
77-
}
78-
79-
if (!document.getElementById('search-wrap')) {
80-
const s = document.createElement('script');
81-
s.src = SITE + '/assets/search.js';
82-
document.head.appendChild(s);
83-
}
84-
})();
1+
"""
2+
Retries thumbnail generation for any example that failed or was skipped
3+
in the last run of generate_example_thumbnails.py.
4+
5+
An example is considered "needs retry" if:
6+
- It exists in examples_js/ but has no entry in manifest.json at all
7+
(crashed before it could be recorded, or was never attempted)
8+
- Its manifest entry has placeholder=True with reason="non_js_source"
9+
is NOT retried -- that's a real content bug, not a transient failure
10+
- Its manifest entry has placeholder=True without a reason IS retried
11+
if --retry-placeholders flag is passed (off by default, since most
12+
placeholders are intentional skips for data-loading sketches)
13+
14+
Usage:
15+
python3 retry_thumbnails.py
16+
python3 retry_thumbnails.py --retry-placeholders
17+
"""
18+
19+
import os
20+
import sys
21+
import json
22+
import argparse
23+
24+
# ── reuse everything from the main script ──────────────────────────────────
25+
sys.path.insert(0, os.path.dirname(__file__))
26+
import generate_example_thumbnails as gen
27+
from playwright.sync_api import sync_playwright
28+
29+
MANIFEST_PATH = os.path.join(gen.OUT_DIR, "manifest.json")
30+
31+
32+
def load_manifest():
33+
if not os.path.exists(MANIFEST_PATH):
34+
print("No manifest found -- run generate_example_thumbnails.py first.")
35+
return {}
36+
with open(MANIFEST_PATH) as f:
37+
entries = json.load(f)
38+
# Key on (section, slug) matching the new multi-section format
39+
return {
40+
(e.get("section", "Basics"), e["slug"]): e
41+
for e in entries
42+
}
43+
44+
45+
def main():
46+
parser = argparse.ArgumentParser()
47+
parser.add_argument(
48+
"--retry-placeholders",
49+
action="store_true",
50+
help="Also retry intentional placeholder entries (sketches that load "
51+
"external assets). Off by default since most are genuine skips.",
52+
)
53+
args = parser.parse_args()
54+
55+
manifest = load_manifest()
56+
all_examples = gen.discover_examples()
57+
58+
to_retry = []
59+
for ex in all_examples:
60+
key = (ex["section"], ex["slug"])
61+
entry = manifest.get(key)
62+
63+
if entry is None:
64+
# Never made it into the manifest at all -- definite failure
65+
to_retry.append((ex, "not in manifest"))
66+
continue
67+
68+
if entry.get("placeholder"):
69+
reason = entry.get("reason", "")
70+
if reason == "non_js_source":
71+
# Real content bug -- skip, can't fix by retrying
72+
continue
73+
if args.retry_placeholders:
74+
to_retry.append((ex, "placeholder retry requested"))
75+
76+
if not to_retry:
77+
print("Nothing to retry. All examples are in the manifest.")
78+
return
79+
80+
print(f"Retrying {len(to_retry)} example(s):")
81+
for ex, reason in to_retry:
82+
print(f" {ex['section']}/{ex['category']}/{ex['slug']} ({reason})")
83+
print()
84+
85+
# Load the full manifest to update it in place
86+
all_entries = list(manifest.values())
87+
all_keys = set(manifest.keys())
88+
89+
asset_base_url, asset_httpd = gen.start_local_asset_server(gen.ASSETS_DIR)
90+
print(f"Serving assets locally at {asset_base_url}")
91+
92+
succeeded = 0
93+
still_failed = []
94+
95+
try:
96+
with sync_playwright() as p:
97+
browser = p.chromium.launch()
98+
99+
for ex, _ in to_retry:
100+
cat_out_dir = os.path.join(gen.OUT_DIR, ex["section"], ex["category"])
101+
os.makedirs(cat_out_dir, exist_ok=True)
102+
out_path = os.path.join(cat_out_dir, ex["slug"] + ".png")
103+
104+
with open(ex["js_path"], errors="replace") as f:
105+
js_code = gen.strip_comment(f.read())
106+
107+
if gen._non_js_source_re.search(js_code):
108+
print(f" SKIP (unconverted source): {ex['section']}/{ex['slug']}")
109+
continue
110+
111+
if gen._data_loading_re.search(js_code):
112+
svg = gen.make_placeholder_svg(ex["name"], gen.THUMB_SIZE)
113+
with open(out_path.replace(".png", ".svg"), "w") as f:
114+
f.write(svg)
115+
new_entry = {**ex, "thumb": ex["slug"] + ".svg", "placeholder": True}
116+
_upsert(all_entries, all_keys, ex, new_entry)
117+
print(f" placeholder: {ex['section']}/{ex['slug']}")
118+
continue
119+
120+
is_no_canvas = bool(gen._no_canvas_re.search(js_code))
121+
122+
try:
123+
ok = gen.render_thumbnail(
124+
browser, js_code, out_path, asset_base_url,
125+
debug_label=f"{ex['section']}/{ex['category']}/{ex['slug']}",
126+
is_no_canvas=is_no_canvas,
127+
)
128+
if ok:
129+
new_entry = {**ex, "thumb": ex["slug"] + ".png", "placeholder": False}
130+
_upsert(all_entries, all_keys, ex, new_entry)
131+
succeeded += 1
132+
print(f" rendered: {ex['section']}/{ex['slug']}")
133+
else:
134+
still_failed.append(ex["slug"])
135+
print(f" FAILED: {ex['section']}/{ex['slug']}")
136+
except Exception as e:
137+
still_failed.append(ex["slug"])
138+
print(f" FAILED ({e}): {ex['section']}/{ex['slug']}")
139+
140+
browser.close()
141+
finally:
142+
asset_httpd.shutdown()
143+
144+
with open(MANIFEST_PATH, "w") as f:
145+
json.dump(all_entries, f, indent=2)
146+
147+
print()
148+
print(f"Succeeded: {succeeded}")
149+
print(f"Still failed: {len(still_failed)}")
150+
for slug in still_failed:
151+
print(f" - {slug}")
152+
print(f"Manifest updated: {MANIFEST_PATH}")
153+
154+
155+
def _upsert(all_entries, all_keys, ex, new_entry):
156+
"""Replace an existing manifest entry in-place, or append if new."""
157+
key = (ex.get("section", "Basics"), ex["slug"])
158+
for i, e in enumerate(all_entries):
159+
if (e.get("section", "Basics"), e["slug"]) == key:
160+
all_entries[i] = new_entry
161+
return
162+
all_entries.append(new_entry)
163+
all_keys.add(key)
164+
165+
166+
if __name__ == "__main__":
167+
main()

0 commit comments

Comments
 (0)