choosing classes
Browse files- static/math.css +8 -1
- static/math.js +29 -3
- tests/test_phone_browser.py +14 -0
static/math.css
CHANGED
|
@@ -212,6 +212,13 @@ html.math-on, html.math-on body{overflow:hidden}
|
|
| 212 |
.mx-class-form input[type="text"],.mx-class-form input[name="name"],.mx-class-form textarea{box-sizing:border-box;width:100%;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:var(--ink);padding:11px 12px;font:inherit}
|
| 213 |
.mx-class-form textarea{min-height:110px;resize:vertical;text-align:left}
|
| 214 |
.mx-class-form fieldset{margin:0}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
.mx-unit-picks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
|
| 216 |
.mx-unit-picks label{display:flex;align-items:center;gap:9px;border:1px solid var(--line);border-radius:11px;padding:10px;cursor:pointer}
|
| 217 |
.mx-unit-picks label:has(input:checked){border-color:var(--mx-accent);background:var(--mx-accent-soft)}
|
|
@@ -263,7 +270,7 @@ html.math-on, html.math-on body{overflow:hidden}
|
|
| 263 |
.mx-qdots i.now{outline:2px solid var(--mx-accent);outline-offset:1px}
|
| 264 |
.mx-count{font:700 11px/1 var(--f-body);padding:4px 7px;border-radius:999px;background:var(--bg);color:var(--muted)}
|
| 265 |
.mx-content-subject{display:flex;flex-direction:column;gap:8px;margin-top:14px}
|
| 266 |
-
.mx-content-subject>h5{display:flex;align-items:baseline;gap:8px;margin:0;font-size:18px;color:var(--exam
|
| 267 |
.mx-content-subject>h5 small{color:var(--muted);font-size:13px;font-weight:500}
|
| 268 |
.mx-content-unit{border:1px solid var(--line);border-radius:12px;background:var(--bg)}
|
| 269 |
.mx-content-unit summary{display:flex;flex-direction:column;gap:3px;padding:12px;cursor:pointer}
|
|
|
|
| 212 |
.mx-class-form input[type="text"],.mx-class-form input[name="name"],.mx-class-form textarea{box-sizing:border-box;width:100%;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:var(--ink);padding:11px 12px;font:inherit}
|
| 213 |
.mx-class-form textarea{min-height:110px;resize:vertical;text-align:left}
|
| 214 |
.mx-class-form fieldset{margin:0}
|
| 215 |
+
.mx-subject-picks{--exam:var(--mx-accent);display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:8px}
|
| 216 |
+
.mx-subject-picks label{display:flex;align-items:center;gap:10px;border:1px solid var(--line);border-radius:13px;padding:9px 10px;cursor:pointer}
|
| 217 |
+
.mx-subject-picks label:has(input:checked),.mx-subject-picks label:has(input:indeterminate){border-color:var(--exam);background:color-mix(in srgb,var(--exam) 14%,var(--surface))}
|
| 218 |
+
.mx-subject-picks .mx-subject-icon{width:34px;height:34px;border-radius:10px;font-size:18px}
|
| 219 |
+
.mx-subject-picks label>span:last-child{display:flex;flex-direction:column;gap:2px;font-weight:700}
|
| 220 |
+
.mx-subject-picks label small{color:var(--muted);font-size:11px;font-weight:500}
|
| 221 |
+
.mx-subject-units[hidden],.mx-no-subject[hidden]{display:none}
|
| 222 |
.mx-unit-picks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
|
| 223 |
.mx-unit-picks label{display:flex;align-items:center;gap:9px;border:1px solid var(--line);border-radius:11px;padding:10px;cursor:pointer}
|
| 224 |
.mx-unit-picks label:has(input:checked){border-color:var(--mx-accent);background:var(--mx-accent-soft)}
|
|
|
|
| 270 |
.mx-qdots i.now{outline:2px solid var(--mx-accent);outline-offset:1px}
|
| 271 |
.mx-count{font:700 11px/1 var(--f-body);padding:4px 7px;border-radius:999px;background:var(--bg);color:var(--muted)}
|
| 272 |
.mx-content-subject{display:flex;flex-direction:column;gap:8px;margin-top:14px}
|
| 273 |
+
.mx-content-subject>h5{display:flex;align-items:baseline;gap:8px;margin:0;font-size:18px;color:var(--exam)}
|
| 274 |
.mx-content-subject>h5 small{color:var(--muted);font-size:13px;font-weight:500}
|
| 275 |
.mx-content-unit{border:1px solid var(--line);border-radius:12px;background:var(--bg)}
|
| 276 |
.mx-content-unit summary{display:flex;flex-direction:column;gap:3px;padding:12px;cursor:pointer}
|
static/math.js
CHANGED
|
@@ -402,19 +402,45 @@ function manageClassAction(action,id){
|
|
| 402 |
|
| 403 |
function openClassEditor(row){
|
| 404 |
const box=sheet.querySelector(".mx-management"), creating=!row;
|
| 405 |
-
const selected=new Set(row?.lessons||
|
|
|
|
| 406 |
box.innerHTML=`<div class="mx-manage-title"><h4>${creating?"כיתה חדשה":`עריכת ${esc(row.name)}`}</h4><button class="mx-btn" type="button" data-class-action="cancel">חזרה לכיתות</button></div>
|
| 407 |
<form class="mx-class-form"><label>שם הכיתה<input name="name" required maxlength="80" autocomplete="off" placeholder="למשל: ט׳ 1" value="${esc(row?.name||"")}"></label>
|
| 408 |
-
<fieldset><legend>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
<label>התלמידים<small>כתובת דוא״ל אחת בכל שורה. תלמיד יכול להשתייך ליותר מכיתה אחת.</small><textarea name="emails" dir="ltr" spellcheck="false" placeholder="student@example.com">${esc((row?.emails||[]).join("\n"))}</textarea></label>
|
| 410 |
<label>מורי הכיתה<small>כתובת דוא״ל אחת בכל שורה. למורים תהיה גישה לכיתה הזו.</small><textarea name="teachers" dir="ltr" spellcheck="false" placeholder="teacher@example.com">${esc((row?.teachers||[]).join("\n"))}</textarea></label>
|
| 411 |
<div class="mx-class-form-actions"><button class="mx-btn solid" type="submit">${creating?"פתיחת הכיתה":"שמירת השינויים"}</button><button class="mx-btn" type="button" data-class-action="cancel">ביטול</button><span role="status" aria-live="polite"></span></div></form>`;
|
| 412 |
const form=box.querySelector("form"), message=form.querySelector('[role="status"]');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
form.querySelector('input[name="name"]').focus();
|
| 414 |
form.onsubmit=async event=>{
|
| 415 |
event.preventDefault();
|
| 416 |
const lessons=[...form.querySelectorAll('input[name="unit"]:checked')].map(input=>input.value);
|
| 417 |
-
if(!lessons.length){message.textContent="בחרו לפחות
|
| 418 |
const lines=name=>form.elements[name].value.split(/[\n,;]+/).map(value=>value.trim().toLowerCase()).filter(Boolean);
|
| 419 |
const body={name:form.elements.name.value.trim(),emails:lines("emails"),teachers:lines("teachers"),lessons};
|
| 420 |
const buttons=form.querySelectorAll("button"); buttons.forEach(button=>button.disabled=true); message.textContent=creating?"פותחים את הכיתה…":"שומרים…";
|
|
|
|
| 402 |
|
| 403 |
function openClassEditor(row){
|
| 404 |
const box=sheet.querySelector(".mx-management"), creating=!row;
|
| 405 |
+
const selected=new Set(row?.lessons||[]); // a new class starts with no subject chosen
|
| 406 |
+
const subjects=[...new Set(G.units.map(unit=>unit.subject))], subjectUnits=subject=>G.units.filter(unit=>unit.subject===subject);
|
| 407 |
box.innerHTML=`<div class="mx-manage-title"><h4>${creating?"כיתה חדשה":`עריכת ${esc(row.name)}`}</h4><button class="mx-btn" type="button" data-class-action="cancel">חזרה לכיתות</button></div>
|
| 408 |
<form class="mx-class-form"><label>שם הכיתה<input name="name" required maxlength="80" autocomplete="off" placeholder="למשל: ט׳ 1" value="${esc(row?.name||"")}"></label>
|
| 409 |
+
<fieldset><legend>מקצועות<small>בחרו את המקצועות שהכיתה לומדת. בחירת מקצוע מסמנת את כל יחידות הלימוד שלו, ואפשר לבטל יחידות שהכיתה לא לומדת.</small></legend>
|
| 410 |
+
<div class="mx-subject-picks">${subjects.map(subject=>{const [icon,tone]=examLooks[subject]||["●","other"], units=subjectUnits(subject);
|
| 411 |
+
return `<label class="exam-${tone}"><input type="checkbox" name="subject" value="${esc(subject)}"><span aria-hidden="true" class="mx-subject-icon">${icon}</span><span>${esc(subject)}<small>${units.length} יחידות</small></span></label>`}).join("")}</div></fieldset>
|
| 412 |
+
<fieldset class="mx-unit-fieldset"><legend>יחידות הלימוד<small>התלמידים יראו במסלול רק את המיומנויות של היחידות שסומנו. מקצוע היחידה קובע אילו מבחנים יוצגו במסלול.</small></legend>
|
| 413 |
+
<p class="mx-note mx-no-subject">בחרו מקצוע כדי לראות את יחידות הלימוד שלו.</p>
|
| 414 |
+
${subjects.map(subject=>`<section class="mx-subject-units" data-subject-units="${esc(subject)}"><h6 class="mx-pick-subject">${esc(subject)}</h6><div class="mx-unit-picks">${subjectUnits(subject).map(unit=>{const skills=unitSkills(unit.id);return `<label class="u-${unit.id}"><input type="checkbox" name="unit" value="${esc(unit.id)}" data-subject="${esc(subject)}"${selected.has(unit.id)?" checked":""}><span>${esc(unit.name)}<small>${skills.length} תת־נושאים · ${questionCount(skills)} שאלות</small></span></label>`}).join("")}</div></section>`).join("")}</fieldset>
|
| 415 |
<label>התלמידים<small>כתובת דוא״ל אחת בכל שורה. תלמיד יכול להשתייך ליותר מכיתה אחת.</small><textarea name="emails" dir="ltr" spellcheck="false" placeholder="student@example.com">${esc((row?.emails||[]).join("\n"))}</textarea></label>
|
| 416 |
<label>מורי הכיתה<small>כתובת דוא״ל אחת בכל שורה. למורים תהיה גישה לכיתה הזו.</small><textarea name="teachers" dir="ltr" spellcheck="false" placeholder="teacher@example.com">${esc((row?.teachers||[]).join("\n"))}</textarea></label>
|
| 417 |
<div class="mx-class-form-actions"><button class="mx-btn solid" type="submit">${creating?"פתיחת הכיתה":"שמירת השינויים"}</button><button class="mx-btn" type="button" data-class-action="cancel">ביטול</button><span role="status" aria-live="polite"></span></div></form>`;
|
| 418 |
const form=box.querySelector("form"), message=form.querySelector('[role="status"]');
|
| 419 |
+
const unitBoxes=subject=>[...form.querySelectorAll('input[name="unit"]')].filter(input=>input.dataset.subject===subject);
|
| 420 |
+
// a subject is chosen while any of its units is; a subject's units stay in view from choosing it until it is cleared
|
| 421 |
+
const open=new Set(subjects.filter(subject=>subjectUnits(subject).some(unit=>selected.has(unit.id))));
|
| 422 |
+
const syncSubjects=()=>{
|
| 423 |
+
form.querySelectorAll('input[name="subject"]').forEach(input=>{
|
| 424 |
+
const boxes=unitBoxes(input.value), on=boxes.filter(box=>box.checked).length;
|
| 425 |
+
input.checked=on>0; input.indeterminate=on>0&&on<boxes.length;
|
| 426 |
+
form.querySelector(`[data-subject-units="${CSS.escape(input.value)}"]`).hidden=!open.has(input.value);
|
| 427 |
+
});
|
| 428 |
+
form.querySelector(".mx-no-subject").hidden=open.size>0;
|
| 429 |
+
};
|
| 430 |
+
form.addEventListener("change",event=>{
|
| 431 |
+
const input=event.target;
|
| 432 |
+
if(input.name==="subject"){
|
| 433 |
+
unitBoxes(input.value).forEach(box=>box.checked=input.checked);
|
| 434 |
+
if(input.checked) open.add(input.value); else open.delete(input.value);
|
| 435 |
+
}
|
| 436 |
+
if(input.name==="subject"||input.name==="unit") syncSubjects();
|
| 437 |
+
});
|
| 438 |
+
syncSubjects();
|
| 439 |
form.querySelector('input[name="name"]').focus();
|
| 440 |
form.onsubmit=async event=>{
|
| 441 |
event.preventDefault();
|
| 442 |
const lessons=[...form.querySelectorAll('input[name="unit"]:checked')].map(input=>input.value);
|
| 443 |
+
if(!lessons.length){message.textContent="בחרו לפחות מקצוע אחד.";return;}
|
| 444 |
const lines=name=>form.elements[name].value.split(/[\n,;]+/).map(value=>value.trim().toLowerCase()).filter(Boolean);
|
| 445 |
const body={name:form.elements.name.value.trim(),emails:lines("emails"),teachers:lines("teachers"),lessons};
|
| 446 |
const buttons=form.querySelectorAll("button"); buttons.forEach(button=>button.disabled=true); message.textContent=creating?"פותחים את הכיתה…":"שומרים…";
|
tests/test_phone_browser.py
CHANGED
|
@@ -152,7 +152,21 @@ def test_class_management_geometry_and_permissions(phone_server, manager):
|
|
| 152 |
playwright.expect(page.locator(".mx-exam-countdown time")).to_contain_text("16 בנובמבר 2026")
|
| 153 |
button.click()
|
| 154 |
playwright.expect(page.locator(".mx-exam-board")).to_contain_text("מתמטיקה")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
page.get_by_role("button", name="עריכה").click()
|
|
|
|
| 156 |
geometry = page.locator('input[name="unit"][value="a"]')
|
| 157 |
geometry.check()
|
| 158 |
page.get_by_role("button", name="שמירת השינויים").click()
|
|
|
|
| 152 |
playwright.expect(page.locator(".mx-exam-countdown time")).to_contain_text("16 בנובמבר 2026")
|
| 153 |
button.click()
|
| 154 |
playwright.expect(page.locator(".mx-exam-board")).to_contain_text("מתמטיקה")
|
| 155 |
+
page.get_by_role("button", name="+ כיתה חדשה").click()
|
| 156 |
+
subject = page.locator('input[name="subject"][value="מתמטיקה"]')
|
| 157 |
+
units = page.locator('input[name="unit"][data-subject="מתמטיקה"]')
|
| 158 |
+
playwright.expect(subject).not_to_be_checked()
|
| 159 |
+
playwright.expect(page.locator('[data-subject-units="מתמטיקה"]')).to_be_hidden()
|
| 160 |
+
subject.check()
|
| 161 |
+
playwright.expect(page.locator('[data-subject-units="מתמטיקה"]')).to_be_visible()
|
| 162 |
+
assert units.count() > 1 and all(units.nth(i).is_checked() for i in range(units.count()))
|
| 163 |
+
units.first.uncheck()
|
| 164 |
+
assert subject.evaluate("input => input.checked && input.indeterminate")
|
| 165 |
+
subject.uncheck()
|
| 166 |
+
assert not any(units.nth(i).is_checked() for i in range(units.count()))
|
| 167 |
+
page.get_by_role("button", name="ביטול").click()
|
| 168 |
page.get_by_role("button", name="עריכה").click()
|
| 169 |
+
playwright.expect(page.locator('input[name="subject"][value="מתמטיקה"]')).to_be_checked()
|
| 170 |
geometry = page.locator('input[name="unit"][value="a"]')
|
| 171 |
geometry.check()
|
| 172 |
page.get_by_role("button", name="שמירת השינויים").click()
|