Lab page: per-variant create/edit modals, rendered conditions, inline lab edit; student page: compact code field, big score, defense wording, header highlight; toasts tinted; p margin

This commit is contained in:
2026-10-02 20:08:26 +03:00
parent e0a1a3d171
commit 9c1664ce00
13 changed files with 304 additions and 214 deletions

View File

@@ -96,6 +96,12 @@ a:not(.btn):hover { color: var(--c-text-link-hover); }
.user { display: flex; align-items: center; gap: 0.75rem; font-size: 0.84375rem; margin-left: auto; }
.nav-user { font-weight: 500; color: var(--c-text); }
.nav-user:hover { color: var(--c-accent); }
.nav-user.defense-active { color: var(--c-accent); }
.nav-user.defense-active::before { content: ""; display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--c-accent); margin-right: 0.4rem; vertical-align: middle; }
.score-big { display: inline-block; margin-left: 0.5rem; font-size: 1.75rem; font-weight: 700; line-height: 1; color: var(--c-tone-pos); font-variant-numeric: tabular-nums; vertical-align: middle; }
.variant-name { font-weight: 600; }
.variant-name b { font-variant-numeric: tabular-nums; }
p { margin-top: 0; }
.theme-btn { display: inline-flex; align-items: center; justify-content: center; padding: 0.3rem 0.5rem; }
.theme-icon { width: 1.05rem; height: 1.05rem; display: block; }
.breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
@@ -274,9 +280,9 @@ tr:last-child td { border-bottom: none; }
}
.toast-item .toast-text { flex: 1 1 auto; min-width: 0; white-space: pre-line; overflow-wrap: anywhere; }
.toast-item .toast-text .code { margin: 0 0.1rem; }
.toast-item.toast-success { border-left-color: var(--c-tone-pos); }
.toast-item.toast-error { border-left-color: var(--c-tone-neg); }
.toast-item.toast-warning { border-left-color: var(--c-badge-warn-text); }
.toast-item.toast-success { border-left-color: var(--c-tone-pos); background: var(--c-badge-ok-bg); }
.toast-item.toast-error { border-left-color: var(--c-accent); background: var(--c-accent-soft); }
.toast-item.toast-warning { border-left-color: var(--c-badge-warn-border); background: var(--c-badge-warn-bg); }
.toast-close {
flex: 0 0 auto; border: none; background: none; cursor: pointer;
color: var(--c-text-muted); font-size: 1rem; line-height: 1; padding: 0 0.1rem;

View File

@@ -363,7 +363,7 @@ function applyDefenseToCell(s) {
: (s.code ? 'код ' + s.code : 'ожидает')));
}
if (s.status === 'ready') {
btn.title = 'Защита завершена: ' + (s.readyReason === 'timeout' ? 'время вышло' : 'студент нажал «Готов»');
btn.title = 'Защита: ' + defenseReasonText(s.readyReason);
if (label) label.textContent = 'готова';
}
}
@@ -388,7 +388,7 @@ function syncDefenseStatuses(sessions, initial) {
var who = (btn && btn.getAttribute('data-student-name')) || 'студент';
var lab = (btn && btn.getAttribute('data-lab-name')) || 'лабораторная';
var reason = s.readyReason === 'timeout' ? 'время вышло' : 'нажал «Готов»';
showToast(who + ' — ' + lab + ': завершил защиту (' + reason + ').');
showToast(who + ' — ' + lab + ': ' + reason + '.');
}
// Обновляем открытый диалог, если он про эту сессию.
@@ -440,9 +440,8 @@ function startDefenseWatch() {
/* ---------- Индивидуальное задание (защита) ---------- */
function defenseReasonText(reason) {
if (reason === 'timeout') return 'время вышло';
if (reason === 'button') return 'студент нажал «Готов»';
return '';
if (reason === 'timeout') return 'Вышло время';
return 'Готов к сдаче';
}
function defenseStatusText(status, code, seconds, reason) {
@@ -453,8 +452,7 @@ function defenseStatusText(status, code, seconds, reason) {
}
if (status === 'started') return 'Студент выполняет работу, осталось ' + fmtSeconds(seconds) + '.';
if (status === 'ready') {
var why = defenseReasonText(reason);
return 'Студент готов к сдаче' + (why ? ' (' + why + ')' : '') + '. Можно начать заново для дополнительного задания.';
return defenseReasonText(reason) + '. Можно начать заново для дополнительного задания.';
}
return '';
}

View File

@@ -0,0 +1,22 @@
/* Страница лабораторной: переключение просмотра/редактирования параметров. */
(function () {
function setMode(editing) {
var view = document.getElementById('lab-description-view');
var form = document.getElementById('lab-edit-form');
if (!view || !form) return;
view.hidden = editing;
form.hidden = !editing;
if (editing) {
var t = form.querySelector('input[name="title"]');
if (t) t.focus();
}
}
document.addEventListener('click', function (e) {
var a = e.target.closest('[data-action]');
if (!a) return;
var kind = a.getAttribute('data-action');
if (kind === 'lab-edit') setMode(true);
if (kind === 'lab-edit-cancel') setMode(false);
});
})();

View File

@@ -84,10 +84,17 @@
/* Активный пункт меню */
function initNavActive() {
var path = window.location.pathname;
document.querySelectorAll('#top-nav .nav-link').forEach(function (a) {
var links = [].slice.call(document.querySelectorAll('#top-nav .nav-link'));
var best = null;
var bestLen = -1;
links.forEach(function (a) {
var href = a.getAttribute('href') || '';
if (path === href || (href !== '/' && path.indexOf(href + '/') === 0)) a.classList.add('active');
if (href === '/') return;
if (path === href || path.indexOf(href + '/') === 0) {
if (href.length > bestLen) { best = a; bestLen = href.length; }
}
});
if (best) best.classList.add('active');
}
/* Серверные уведомления: автоскрытие и кнопка закрытия */

View File

@@ -29,6 +29,12 @@ document.querySelectorAll('.defense-timer').forEach(function (el) {
if (left > 0) {
render(left);
startTick();
markDefense(true);
}
function markDefense(active) {
var name = document.querySelector('.nav-user');
if (name) name.classList.toggle('defense-active', active);
}
if (labId) {
@@ -39,9 +45,11 @@ document.querySelectorAll('.defense-timer').forEach(function (el) {
if (!res || !res.ok) return;
// У сессии закончилось время.
if (res.status !== 'started') {
markDefense(false);
finish();
return;
}
markDefense(true);
var server = parseInt(res.secondsLeft || '0', 10);
if (server <= 0) { finish(); return; }
// Подстраиваем локальный отсчёт под серверное время.