Show Color it optional step as complete if done, make levels accordion style collapsible
This commit is contained in:
1 parent
e868e2a2e7
commit
5a4d42137b
2 files changed
+33
-11
No files matched your search
+29
-11
@@ -17,7 +17,7 @@ export default async function LearnPage() {
|
||||
const isDone = (l: Lesson) => {
|
||||
if (!user) return false;
|
||||
if (l.badgeKey && hasBadge(user.id, l.badgeKey)) return true;
|
||||
return l.phase === "extra" ? false : doneCount(l) >= stepCount(l);
|
||||
return doneCount(l) >= stepCount(l);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -27,12 +27,16 @@ export default async function LearnPage() {
|
||||
<h1>Learn to draw 🎨</h1>
|
||||
<p className="muted">
|
||||
Each picture has an <strong>Outline</strong> lesson and a <strong>Details</strong> lesson (earn the badge), plus an optional <strong>Color it!</strong> lesson.
|
||||
{!user && " Log in to track progress and earn badges."}
|
||||
{!user && " Log in to track your progress and earn badges."}
|
||||
</p>
|
||||
|
||||
<div className="stack" style={{ marginTop: 18 }}>
|
||||
{LEVELS.map((lvl, idx) => {
|
||||
const subjects = getSubjects(lvl.key);
|
||||
const complete = new Map<string, boolean>(subjects.map((s) => [s.key, s.lessons.every((l) => isDone(l))]));
|
||||
// The first not-yet-finished phased subject is expanded; completed ones collapse.
|
||||
const firstIncomplete = subjects.find((s) => (s.lessons.length > 1 || !!s.lessons[0].phase) && !complete.get(s.key))?.key;
|
||||
|
||||
return (
|
||||
<div className="card" key={lvl.key}>
|
||||
<div className="row" style={{ justifyContent: "space-between" }}>
|
||||
@@ -44,8 +48,8 @@ export default async function LearnPage() {
|
||||
<div className="stack" style={{ marginTop: 6 }}>
|
||||
{subjects.map((subj) => {
|
||||
const phased = subj.lessons.length > 1 || !!subj.lessons[0].phase;
|
||||
|
||||
if (!phased) {
|
||||
// legacy single lesson
|
||||
const l = subj.lessons[0];
|
||||
const done = isDone(l);
|
||||
const inProg = !done && doneCount(l) > 0;
|
||||
@@ -53,26 +57,28 @@ export default async function LearnPage() {
|
||||
<Link key={subj.key} href={`/learn/${l.level}/${l.slug}`} className="card" style={subCard(done)}>
|
||||
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
|
||||
<strong>{subj.emoji} {l.title}</strong>
|
||||
<span>{done ? <span className="pill active">✓ Done</span> : inProg ? <span className="pill pending">Step {Math.min(doneCount(l) + 1, stepCount(l))}/{stepCount(l)}</span> : <span className="muted" style={{ fontSize: "0.85rem" }}>{stepCount(l)} steps</span>}</span>
|
||||
{done ? <span className="pill active">✓ Done</span> : inProg ? <span className="pill pending">Step {Math.min(doneCount(l) + 1, stepCount(l))}/{stepCount(l)}</span> : <span className="muted" style={{ fontSize: "0.85rem" }}>{stepCount(l)} steps</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
// phased subject (Outline / Details / Color it)
|
||||
|
||||
const byPhase = (p: string) => subj.lessons.find((l) => l.phase === p);
|
||||
const outline = byPhase("outline");
|
||||
const detail = byPhase("detail");
|
||||
const extra = byPhase("extra");
|
||||
const outlineDone = outline ? isDone(outline) : false;
|
||||
const detailDone = detail ? isDone(detail) : false;
|
||||
const subjComplete = !!complete.get(subj.key);
|
||||
|
||||
const phaseCard = (l: Lesson | undefined, locked: boolean, optional = false) => {
|
||||
if (!l) return null;
|
||||
const done = isDone(l);
|
||||
const inProg = !done && !locked && doneCount(l) > 0;
|
||||
const inner = (
|
||||
<div style={{ position: "relative" }}>
|
||||
<div>
|
||||
<strong style={{ display: "block" }}>{PHASE_LABEL[l.phase ?? ""]}{optional ? " (optional)" : ""}</strong>
|
||||
<span style={{ fontSize: "0.82rem" }} className="muted">
|
||||
<span className="muted" style={{ fontSize: "0.82rem" }}>
|
||||
{locked ? "🔒 Locked" : done ? "✓ Done" : inProg ? `Step ${Math.min(doneCount(l) + 1, stepCount(l))}/${stepCount(l)}` : l.phase === "extra" ? "Add color" : `${stepCount(l)} steps`}
|
||||
</span>
|
||||
</div>
|
||||
@@ -83,15 +89,27 @@ export default async function LearnPage() {
|
||||
<Link key={l.slug} href={`/learn/${l.level}/${l.slug}`} style={{ ...phaseBox, textDecoration: "none", color: "inherit", borderColor: done ? "var(--leaf)" : "var(--line)" }}>{inner}</Link>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={subj.key} className="card" style={{ border: "2px solid var(--line)", boxShadow: "none" }}>
|
||||
<strong style={{ fontSize: "1.1rem" }}>{subj.emoji} {subj.name}</strong>
|
||||
<div style={{ display: "grid", gap: 10, gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", marginTop: 10 }}>
|
||||
<details
|
||||
key={subj.key}
|
||||
open={subj.key === firstIncomplete}
|
||||
style={{ border: subjComplete ? "2px solid var(--leaf)" : "2px solid var(--line)", borderRadius: 14, padding: "10px 14px", background: "#fff" }}
|
||||
>
|
||||
<summary style={{ cursor: "pointer", fontWeight: 800, fontSize: "1.05rem" }}>
|
||||
{subj.emoji} {subj.name}
|
||||
{subjComplete ? (
|
||||
<span className="pill active" style={{ marginLeft: 8 }}>✓ Done</span>
|
||||
) : outlineDone || detailDone ? (
|
||||
<span className="pill pending" style={{ marginLeft: 8 }}>In progress</span>
|
||||
) : null}
|
||||
</summary>
|
||||
<div style={{ display: "grid", gap: 10, gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", marginTop: 12 }}>
|
||||
{phaseCard(outline, false)}
|
||||
{phaseCard(detail, !outlineDone)}
|
||||
{phaseCard(extra, !detailDone, true)}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -217,6 +217,10 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
|
||||
function toggleVisible(id: number) { const l = layersRef.current.find((x) => x.id === id); if (l) { l.visible = !l.visible; composite(); force((n) => n + 1); scheduleSave(); } }
|
||||
|
||||
async function finish() {
|
||||
// Record completion of the Color it! lesson so the Learn page can mark it Done.
|
||||
if (loggedIn) {
|
||||
try { await fetch("/api/progress", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ level: meta.level, sublevel: meta.sublevel, step: 1 }) }); } catch { /* */ }
|
||||
}
|
||||
if (hasContent()) { const data = flatten(true); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); }
|
||||
setDone(true);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user