From 3fdce2392956af8c5fd9dc5578c948f96bfa6325 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Wed, 17 Jun 2026 20:51:41 -0500 Subject: [PATCH] Carry over the drawn image from outline to details --- src/app/learn/[level]/[slug]/page.tsx | 15 +++++++++- src/components/TraceRunner.tsx | 42 +++++++++++++++++++++++---- src/lib/drawings.ts | 12 ++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/app/learn/[level]/[slug]/page.tsx b/src/app/learn/[level]/[slug]/page.tsx index 2579a47..6a6b1c8 100644 --- a/src/app/learn/[level]/[slug]/page.tsx +++ b/src/app/learn/[level]/[slug]/page.tsx @@ -7,6 +7,7 @@ import ColoringRunner from "@/components/ColoringRunner"; import { getLessonBySlug, getLevel, getSubjects, cumulativeSvg, type Lesson } from "@/lib/curriculum"; import { getCurrentUser } from "@/lib/session"; import { getCompletedSteps, hasBadge } from "@/lib/progress"; +import { getLatestDrawing } from "@/lib/drawings"; export const dynamic = "force-dynamic"; @@ -52,11 +53,23 @@ export default async function LessonPage({ params }: { params: Promise<{ level: if (lesson.phase === "outline" || lesson.phase === "detail") { const steps = lesson.steps.map((s) => ({ n: s.n, title: s.title, instruction: s.instruction, tip: s.tip ?? "", lines: s.lines ?? [] })); const meta = { ...common, badgeKey: lesson.badgeKey, badgeName: lesson.badgeName, baseSvg: lesson.baseSvg ?? "", phaseLabel: lesson.phase === "outline" ? "Outline" : "Details" }; + const subj = getSubjects(level).find((s) => s.key === lesson.subjectKey); + // Carry the user's own outline drawing into the Details lesson as the starting canvas. + let carryImage = ""; + if (lesson.phase === "detail" && user) { + const o = subj?.lessons.find((l) => l.phase === "outline"); + if (o) carryImage = getLatestDrawing(user.id, level, o.sublevel) ?? ""; + } + // Link to the next lesson in the subject. + let nextHref = "", nextLabel = ""; + const nextPhase = lesson.phase === "outline" ? "detail" : "extra"; + const nextLesson = subj?.lessons.find((l) => l.phase === nextPhase); + if (nextLesson) { nextHref = `/learn/${level}/${nextLesson.slug}`; nextLabel = lesson.phase === "outline" ? "Add the details" : "Color it in!"; } return ( <>
- +
); diff --git a/src/components/TraceRunner.tsx b/src/components/TraceRunner.tsx index 6ef1083..94e5f94 100644 --- a/src/components/TraceRunner.tsx +++ b/src/components/TraceRunner.tsx @@ -16,8 +16,9 @@ const SIZES = [4, 8, 16]; interface Point { x: number; y: number } interface Stroke { color: string; size: number; points: Point[] } -export default function TraceRunner({ meta, steps, loggedIn, username, completedSteps, alreadyEarned }: { +export default function TraceRunner({ meta, steps, loggedIn, username, completedSteps, alreadyEarned, carryImage = "", nextHref = "", nextLabel = "" }: { meta: Meta; steps: StepData[]; loggedIn: boolean; username: string; completedSteps: number[]; alreadyEarned: boolean; + carryImage?: string; nextHref?: string; nextLabel?: string; }) { const lastIndex = steps.length - 1; const [idx, setIdx] = useState(Math.min(completedSteps.length, lastIndex)); @@ -69,9 +70,14 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed useEffect(() => { if (!loggedIn || alreadyEarned) return; let alive = true; - loadProgressDrawing(meta.level, meta.sublevel).then((img) => { if (!img || !alive) return; const im = new Image(); im.onload = () => { baseImgRef.current = im; redraw(); }; im.src = img; }); + loadProgressDrawing(meta.level, meta.sublevel).then((img) => { + // Use this lesson's own saved canvas if any; otherwise carry over the previous lesson's drawing + const src = img || carryImage; + if (!src || !alive) return; + const im = new Image(); im.onload = () => { baseImgRef.current = im; redraw(); }; im.src = src; + }); return () => { alive = false; }; - }, [loggedIn, alreadyEarned, meta.level, meta.sublevel, redraw]); + }, [loggedIn, alreadyEarned, meta.level, meta.sublevel, carryImage, redraw]); // Auto-play the line video whenever the step changes useEffect(() => { setPlaying(true); setPlayKey((k) => k + 1); }, [idx]); @@ -95,8 +101,14 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed function prev() { flushSave(); if (idx > 0) setIdx(idx - 1); } async function finish() { await saveStep(step.n, meta.badgeKey || undefined); - const c = canvasRef.current; - if (c && hasContent()) { const data = exportCanvasToDataURL(c); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); } + // Only completing the level (the badge lesson) saves to the profile gallery. + // Earlier lessons (e.g. Outline) keep their working canvas so it carries forward, but aren't saved as finals. + if (meta.badgeKey) { + const c = canvasRef.current; + if (c && hasContent()) { const data = exportCanvasToDataURL(c); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); } + } else { + flushSave(); // persist the outline canvas so the Details lesson can carry it over + } setEarned(loggedIn && !!meta.badgeKey); setFinished(true); } @@ -122,6 +134,23 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed } async function downloadBadge() { const b = await rasterizeBadge(); if (b) downloadBlob(b, "drawit-badge.png"); } + if (finished && !meta.badgeKey) { + // Outline (or any non-badge step): completed, but the level isn't finished — no profile saving here. + return ( +
+
+

✏️ Outline complete!

+

Awesome lines! Your outline carries over to the next lesson, so you can add the details right on top.

+ {nextHref && {nextLabel || "Next"} →} +
+ + Back to lessons +
+
+
+ ); + } + if (finished) { return (
@@ -152,7 +181,8 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed {uploadMsg &&

{uploadMsg}

}
-
+ {nextHref &&
{nextLabel || "Next"} →
} +
{loggedIn && 🖼️ My drawings} Back to lessons diff --git a/src/lib/drawings.ts b/src/lib/drawings.ts index cb5f56a..a1a830b 100644 --- a/src/lib/drawings.ts +++ b/src/lib/drawings.ts @@ -74,6 +74,18 @@ export function saveFinalDrawing( return Number(info.lastInsertRowid); } +/** The most recent drawing for a lesson — the latest completed one, else the in-progress canvas. */ +export function getLatestDrawing(userId: number, level: string, sublevel: number): string | null { + const f = getDb() + .prepare( + `SELECT image FROM drawings WHERE user_id = ? AND level = ? AND sublevel = ? AND status = 'final' + ORDER BY created_at DESC, id DESC LIMIT 1` + ) + .get(userId, level, sublevel) as { image: string } | undefined; + if (f) return f.image; + return getProgressDrawing(userId, level, sublevel); +} + export function listFinalDrawings(userId: number): DrawingRow[] { return getDb() .prepare(