Skip to content

Commit 7241e9e

Browse files
committed
레슨 클릭 실행 - 커리큘럼과 브라우저 런타임 연결(P3.5)
커리큘럼 생성기가 각 레슨의 첫 실행 스니펫을 추출(467/472). /learn에서 레슨 카드를 누르면 그 코드가 상단 실행기로 로드되고 스크롤한다. PythonRunner에 외부 코드 로드(load) 배선 추가. 이제 472개 레슨이 브라우저에서 진짜 실행된다. 전체 빌드 green(83 routes).
1 parent ef11f2f commit 7241e9e

5 files changed

Lines changed: 1052 additions & 492 deletions

File tree

landing/scripts/generateCurriculum.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ function domainOf(fileRel) {
3636
return fileRel.split(/[\\/]/)[0];
3737
}
3838

39+
// 레슨 문서에서 첫 실행 코드(snippet)를 재귀로 찾는다. sections[].snippet 등 위치가 다양하다.
40+
function findFirstSnippet(node, depth = 0) {
41+
if (!node || depth > 8) return "";
42+
if (Array.isArray(node)) {
43+
for (const item of node) {
44+
const found = findFirstSnippet(item, depth + 1);
45+
if (found) return found;
46+
}
47+
return "";
48+
}
49+
if (typeof node === "object") {
50+
if (typeof node.snippet === "string" && node.snippet.trim().length > 0) return node.snippet;
51+
for (const value of Object.values(node)) {
52+
const found = findFirstSnippet(value, depth + 1);
53+
if (found) return found;
54+
}
55+
}
56+
return "";
57+
}
58+
3959
const files = existsSync(CURRICULA) ? walkYaml(CURRICULA) : [];
4060
const domains = new Map();
4161
let lessonCount = 0;
@@ -61,6 +81,7 @@ for (const file of files) {
6181
emoji: (doc.intro && doc.intro.emoji) || "",
6282
direction: (doc.intro && doc.intro.direction) || (meta.seo && meta.seo.description) || "",
6383
slug: `${domain}/${track}/${meta.id}`,
84+
code: findFirstSnippet(doc).slice(0, 800),
6485
};
6586
if (!domains.has(domain)) domains.set(domain, new Map());
6687
const tracks = domains.get(domain);

landing/src/components/pythonRunner.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// pythonRunner.jsx - 브라우저에서 진짜 Python을 실행하는 위젯(pyproc).
22
// 단일 boot()+runAsync() 경로는 SharedArrayBuffer/COOP-COEP가 필요 없어 GitHub Pages에서도 돈다.
33
// pyproc은 첫 실행 클릭에서 lazy import(코드 스플릿 + 큰 런타임 다운로드 지연). SSR 안전(렌더 시 브라우저 API 미접근).
4-
import { useRef, useState } from "react";
4+
import { useEffect, useRef, useState } from "react";
55
import { Play, Loader2, RotateCcw } from "lucide-react";
66
import { Button } from "@astryxdesign/core/Button";
77
import { Badge } from "@astryxdesign/core/Badge";
@@ -21,13 +21,21 @@ const STATUS_LABEL = {
2121
ready: "준비됨",
2222
};
2323

24-
export function PythonRunner({ initialCode = DEFAULT_CODE }) {
24+
export function PythonRunner({ initialCode = DEFAULT_CODE, load }) {
2525
const [code, setCode] = useState(initialCode);
2626
const [output, setOutput] = useState("");
2727
const [status, setStatus] = useState("idle");
2828
const rtRef = useRef(null);
2929
const linesRef = useRef([]);
3030

31+
// 외부(레슨 카드)에서 코드를 실어 보내면 에디터에 로드한다. load.nonce가 바뀔 때마다 반영.
32+
useEffect(() => {
33+
if (load && typeof load.code === "string" && load.code.length > 0) {
34+
setCode(load.code);
35+
setOutput("");
36+
}
37+
}, [load]);
38+
3139
async function ensureRuntime() {
3240
if (rtRef.current) return rtRef.current;
3341
setStatus("booting");

0 commit comments

Comments
 (0)