Add beginner levels focusing on shapes and 3d
This commit is contained in:
1 parent
ee8e218b7d
commit
be470268ab
9 files changed
+538
-152
No files matched your search
@@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { loadProgressDrawing, saveProgressDrawing, saveFinalDrawing, fileToDataURL, loadDrawingAsLayer } from "./drawingPersistence";
|
||||
|
||||
interface Meta { level: string; levelName: string; sublevel: number; title: string; emoji: string; intro: string; baseSvg: string }
|
||||
interface Meta { level: string; levelName: string; sublevel: number; title: string; emoji: string; intro: string; baseSvg: string; phaseLabel?: string; hintSvg?: string }
|
||||
|
||||
const W = 880;
|
||||
const H = 660;
|
||||
@@ -32,10 +32,11 @@ function newLayer(id: number, name: string): Layer {
|
||||
return { id, name, visible: true, canvas: c };
|
||||
}
|
||||
|
||||
export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned, userSketch = "" }: {
|
||||
meta: Meta; loggedIn: boolean; username: string; alreadyEarned: boolean; userSketch?: string;
|
||||
export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned, userSketch = "", carryImage = "" }: {
|
||||
meta: Meta; loggedIn: boolean; username: string; alreadyEarned: boolean; userSketch?: string; carryImage?: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const phaseLabel = meta.phaseLabel || "Color it!";
|
||||
// Which line art to color: the stock template or the child's own sketch.
|
||||
// If they have no sketch of their own, skip the choice and use the stock art.
|
||||
const [sketch, setSketch] = useState<"stock" | "yours" | null>(userSketch ? null : "stock");
|
||||
@@ -115,18 +116,24 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
|
||||
return () => { alive = false; };
|
||||
}, [sketch, meta.baseSvg, userSketch]);
|
||||
|
||||
// Restore saved coloring into the bottom layer
|
||||
// Restore saved coloring into the bottom layer; if none, seed with the carry image
|
||||
// (e.g. the Light phase starts from the colored drawing).
|
||||
useEffect(() => {
|
||||
if (!loggedIn) { composite(); return; }
|
||||
let alive = true;
|
||||
loadProgressDrawing(meta.level, meta.sublevel).then((data) => {
|
||||
if (!data || !alive) { composite(); return; }
|
||||
const seed = (data: string) => {
|
||||
const im = new Image();
|
||||
im.onload = () => { layersRef.current[0].canvas.getContext("2d")!.drawImage(im, 0, 0, W, H); composite(); };
|
||||
im.onload = () => { if (alive) { layersRef.current[0]?.canvas.getContext("2d")!.drawImage(im, 0, 0, W, H); composite(); } };
|
||||
im.src = data;
|
||||
};
|
||||
if (!loggedIn) { if (carryImage) seed(carryImage); else composite(); return; }
|
||||
loadProgressDrawing(meta.level, meta.sublevel).then((data) => {
|
||||
if (!alive) return;
|
||||
if (data) seed(data);
|
||||
else if (carryImage) seed(carryImage);
|
||||
else composite();
|
||||
});
|
||||
return () => { alive = false; };
|
||||
}, [loggedIn, meta.level, meta.sublevel, composite]);
|
||||
}, [loggedIn, meta.level, meta.sublevel, composite, carryImage]);
|
||||
|
||||
function flatten(withTemplate: boolean): string {
|
||||
const c = document.createElement("canvas"); c.width = W; c.height = H;
|
||||
@@ -261,7 +268,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
|
||||
if (sketch === null) {
|
||||
return (
|
||||
<div>
|
||||
<div className="muted" style={{ fontWeight: 700 }}>{meta.levelName} · Color it!</div>
|
||||
<div className="muted" style={{ fontWeight: 700 }}>{meta.levelName} · {phaseLabel}</div>
|
||||
<h1 style={{ margin: "2px 0" }}>{meta.emoji} {meta.title}</h1>
|
||||
<p className="muted">Which drawing would you like to color in?</p>
|
||||
<div style={{ display: "grid", gap: 16, gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", marginTop: 12 }}>
|
||||
@@ -320,7 +327,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
|
||||
<div>
|
||||
<div className="row" style={{ justifyContent: "space-between", alignItems: "flex-start" }}>
|
||||
<div>
|
||||
<div className="muted" style={{ fontWeight: 700 }}>{meta.levelName} · Color it!</div>
|
||||
<div className="muted" style={{ fontWeight: 700 }}>{meta.levelName} · {phaseLabel}</div>
|
||||
<h1 style={{ margin: "2px 0" }}>{meta.emoji} {meta.title}</h1>
|
||||
</div>
|
||||
<Link className="btn ghost" href={`/report?level=${meta.level}&sublevel=${meta.sublevel}`} style={{ minHeight: 40 }}>Report a problem</Link>
|
||||
@@ -333,6 +340,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
|
||||
<canvas ref={dispRef} width={W} height={H} onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}
|
||||
style={{ position: "absolute", inset: 0, width: "100%", height: "100%", touchAction: "none", cursor: tool === "fill" ? "pointer" : "crosshair" }} />
|
||||
{overlayUrl && <img src={overlayUrl} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "fill", pointerEvents: "none" }} />}
|
||||
{meta.hintSvg && <svg viewBox="0 0 400 300" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }} dangerouslySetInnerHTML={{ __html: meta.hintSvg }} />}
|
||||
</div>
|
||||
|
||||
{/* Tools */}
|
||||
|
||||
Reference in new issue
Block a user