// The path: a trail of lessons. Each lesson is a hub with its skills as numbered stars in a ring around it; the owl perches on the // suggested next skill. Every star is a link to its exercise screen (#/skill/) and says its state in a symbol and a word. import { api, swr } from "./api.js"; import { esc, fmt } from "./math.js"; import { owl, ringSvg, spark } from "./sky.js"; import { backToSubjects, forSubject, goalBar, shortDate, tabs } from "./ui.js"; export const STATES = { not_started: { symbol: "○", word: "לא התחלת" }, learning: { symbol: "◐", word: "בלמידה" }, mastered: { symbol: "✓", word: "נשלטה" }, }; const NEXT = "ממשיכים כאן"; const FADING = "דועך"; const plain = text => String(text ?? "").replace(/\$/g, ""); function star(skill, i, n, isNext) { const t = (-90 + 360 * i / n) * Math.PI / 180, base = STATES[skill.state] || STATES.not_started; const symbol = skill.faded ? `${base.symbol} ◌` : base.symbol, word = skill.faded ? `${base.word}, ${FADING}` : base.word; return ` ${spark()}${isNext ? `${owl(30)}` : ""} ${fmt(skill.short)} ${word}${isNext ? " · " + NEXT : ""}`; } /** The lesson's quiz, a node on the trail after its ring: always open, its state in words. */ function quizNode(l) { const { best, open } = l.quiz || {}; const state = [best !== null && best !== undefined ? `✓ הציון: ${best}` : "○ עוד לא נעשה", open ? "◐ בוחן פתוח" : ""].filter(Boolean).join(" · "); return `בוחן${state}`; } /** "הושלם", or, for a lesson whose date has passed without it being completed, the marker with the date in words. */ function lessonStatus(l) { if (l.completed) return `

הושלם${l.passed ? " (עברת את הבוחן)" : ""}

`; if (l.behind) return `

היה אמור להסתיים ב־${shortDate(l.due_date)}

`; return ""; } const behindNote = n => n ? `

${n === 1 ? "שיעור אחד בפיגור" : `${n} שיעורים בפיגור`} לפי התכנית

` : ""; function lesson(l, next) { const n = l.skills.length; return `
${ringSvg(l.skills.map(s => s.state))}

${fmt(l.name)}

${l.mastered} מתוך ${n} נשלטו${l.completed ? " ✓" : ""}
${l.skills.map((s, i) => star(s, i, n, s.id === next)).join("")}
${lessonStatus(l)}
${quizNode(l)}`; } export function pathHtml(path) { return `${tabs("path")}${backToSubjects()}

העמקה: המסלול המלא

כאן מתרגלים כל מיומנות לבד, לעומק. את השיעורים משחקים במפה שבבית.

${goalBar(path.today, path.streak)}${behindNote(path.behind)}
${owl(44)}התחלה
${path.lessons.map(l => lesson(l, path.next)).join("")}
סוף המסלול
`; } /** Draw the path. `scroll`: bring the next skill to the middle of the screen (when the path opens); a refresh keeps the place. */ export const show = (view, { scroll = true } = {}) => { let first = true; // a refresh of the same screen keeps the place return swr(forSubject("/api/path"), path => { const result = drawPath(view, path, scroll && first); first = false; return result; }); }; function drawPath(view, path, scroll) { const place = window.scrollY; view(pathHtml(path)); if (scroll) document.querySelector(".star.next")?.scrollIntoView({ block: "center" }); else window.scrollTo(0, place); return path; } /** Math's card keeps the course's full name; other subjects show their own name. */ const cardName = s => s.id === "math" ? "מתמטיקה כיתה ט - מצוינות" : s.name; /** The first screen: a card per subject of the student's classes, math first; each bar counts that subject's mastered skills. */ export async function showSubjects(view) { const subjects = await api("/api/my-subjects"); view(`

מה נתרגל היום?

${subjects.map(s => ` ${esc(cardName(s))} ${s.mastered} מתוך ${s.total} מיומנויות`).join("")}
`); return subjects; }