feat(sim-builder): фаза 6 — раздача классу, клон, шаблоны, привязка к программе (custom_sims)
This commit is contained in:
@@ -715,6 +715,21 @@ const SIMS = [
|
||||
|
||||
var _EDIT_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>';
|
||||
var _DEL_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
var _SHARE_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>';
|
||||
var _CLONE_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||
var _PUB_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
||||
var _UNPUB_ICON = '<svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><path d="M2 2l20 20"/><path d="M12 2a15.3 15.3 0 0 1 4 10c0 1.3-.2 2.6-.5 3.8M6.5 6.5A15.3 15.3 0 0 0 12 22a15.3 15.3 0 0 0 3.3-5"/><path d="M2 12h7m6 0h7"/></svg>';
|
||||
|
||||
function _isTeacherUser() {
|
||||
try { return typeof user !== 'undefined' && user && (user.role === 'teacher' || user.role === 'admin'); }
|
||||
catch (e) { return false; }
|
||||
}
|
||||
function _btn(act, id, html, extra, title) {
|
||||
return '<button type="button" data-act="' + act + '" data-id="' + _esc(id) + '" ' +
|
||||
(title ? 'title="' + _esc(title) + '" ' : '') +
|
||||
'style="display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:.78rem;font-weight:700;padding:7px 10px;border-radius:10px;cursor:pointer;' + (extra || '') + '">' +
|
||||
html + '</button>';
|
||||
}
|
||||
|
||||
function _cardHtml(m) {
|
||||
var owner = _isOwner(m);
|
||||
@@ -726,14 +741,26 @@ const SIMS = [
|
||||
else if (owner) badges += '<span style="display:inline-flex;align-items:center;gap:4px;font-size:.62rem;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:3px 9px;border-radius:99px;background:rgba(255,255,255,.06);color:var(--text-3);border:1px solid rgba(255,255,255,.14)">Черновик</span>';
|
||||
var actions = '';
|
||||
if (owner) {
|
||||
var STYLE_PRI = 'flex:1;background:rgba(155,93,229,.12);color:var(--violet);border:1px solid rgba(155,93,229,.3)';
|
||||
var STYLE_GHOST = 'background:rgba(255,255,255,.05);color:var(--text-2);border:1px solid rgba(255,255,255,.16)';
|
||||
var STYLE_DEL = 'background:rgba(244,91,105,.1);color:#f45b69;border:1px solid rgba(244,91,105,.28)';
|
||||
var pubBtn = published
|
||||
? _btn('unpublish', m.id, _UNPUB_ICON, STYLE_GHOST, 'Снять с публикации')
|
||||
: _btn('publish', m.id, _PUB_ICON, STYLE_GHOST, 'Опубликовать');
|
||||
actions =
|
||||
'<div style="display:flex;gap:8px;margin-top:12px">' +
|
||||
'<button type="button" data-act="edit" data-id="' + _esc(m.id) + '" ' +
|
||||
'style="flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:.78rem;font-weight:700;padding:7px 10px;border-radius:10px;cursor:pointer;background:rgba(155,93,229,.12);color:var(--violet);border:1px solid rgba(155,93,229,.3)">' +
|
||||
_EDIT_ICON + 'Редактировать</button>' +
|
||||
'<button type="button" data-act="del" data-id="' + _esc(m.id) + '" ' +
|
||||
'style="display:inline-flex;align-items:center;justify-content:center;padding:7px 11px;border-radius:10px;cursor:pointer;background:rgba(244,91,105,.1);color:#f45b69;border:1px solid rgba(244,91,105,.28)" title="Удалить">' +
|
||||
_DEL_ICON + '</button>' +
|
||||
_btn('edit', m.id, _EDIT_ICON + 'Редактировать', STYLE_PRI) +
|
||||
_btn('del', m.id, _DEL_ICON, STYLE_DEL, 'Удалить') +
|
||||
'</div>' +
|
||||
'<div style="display:flex;gap:8px;margin-top:8px">' +
|
||||
_btn('share', m.id, _SHARE_ICON + 'Раздать классу', STYLE_GHOST + ';flex:1') +
|
||||
pubBtn +
|
||||
'</div>';
|
||||
} else if (published && _isTeacherUser()) {
|
||||
actions =
|
||||
'<div style="display:flex;gap:8px;margin-top:12px">' +
|
||||
_btn('clone', m.id, _CLONE_ICON + 'Клонировать к себе',
|
||||
'flex:1;background:rgba(155,93,229,.12);color:var(--violet);border:1px solid rgba(155,93,229,.3)') +
|
||||
'</div>';
|
||||
}
|
||||
var preview = '<svg class="sim-preview" viewBox="0 0 300 140" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">' +
|
||||
@@ -792,6 +819,14 @@ const SIMS = [
|
||||
location.href = '/sim-builder?id=' + encodeURIComponent(id);
|
||||
} else if (act === 'del') {
|
||||
del(id);
|
||||
} else if (act === 'share') {
|
||||
shareToClass(id);
|
||||
} else if (act === 'clone') {
|
||||
clone(id);
|
||||
} else if (act === 'publish') {
|
||||
setStatus(id, 'published');
|
||||
} else if (act === 'unpublish') {
|
||||
setStatus(id, 'draft');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -818,6 +853,69 @@ const SIMS = [
|
||||
});
|
||||
}
|
||||
|
||||
// Опубликовать / снять с публикации (владельцу). PUT status.
|
||||
function setStatus(dbid, status) {
|
||||
if (!window.LS || !LS.customSimUpdate) return;
|
||||
LS.customSimUpdate(dbid, { status: status }).then(function () {
|
||||
if (_meta[dbid]) _meta[dbid].status = status;
|
||||
renderSection(_catFilter);
|
||||
if (LS.toast) LS.toast(status === 'published' ? 'Опубликовано' : 'Снято с публикации', 'success');
|
||||
}).catch(function (e) {
|
||||
if (LS.toast) LS.toast((e && e.message) || 'Не удалось изменить статус', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Клонировать чужую (published) симуляцию к себе как черновик и открыть в билдере.
|
||||
function clone(dbid) {
|
||||
if (!window.LS || !LS.customSimClone) return;
|
||||
LS.customSimClone(dbid).then(function (res) {
|
||||
var newId = res && res.id;
|
||||
if (newId) {
|
||||
if (LS.toast) LS.toast('Скопировано в ваши черновики', 'success');
|
||||
location.href = '/sim-builder?id=' + encodeURIComponent(newId);
|
||||
}
|
||||
}).catch(function (e) {
|
||||
if (LS.toast) LS.toast((e && e.message) || 'Не удалось клонировать', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Раздать классу: модалка выбора класса -> LS.customSimShare (авто-публикует
|
||||
// и шлёт уведомление ученикам со ссылкой /lab?sim=custom:<id>).
|
||||
function shareToClass(dbid) {
|
||||
if (!window.LS || !LS.customSimShare || !LS.getClasses || !LS.modal) return;
|
||||
LS.getClasses().then(function (classes) {
|
||||
if (!Array.isArray(classes) || !classes.length) {
|
||||
if (LS.toast) LS.toast('Нет классов для раздачи', 'warn');
|
||||
return;
|
||||
}
|
||||
var opts = classes.map(function (c) {
|
||||
return '<option value="' + _esc(c.id) + '">' + _esc(c.name) + '</option>';
|
||||
}).join('');
|
||||
var content = '<div style="display:flex;flex-direction:column;gap:8px">' +
|
||||
'<label style="font-size:.8rem;color:var(--text-3)">Класс</label>' +
|
||||
'<select id="cs-share-class" style="width:100%;box-sizing:border-box;padding:9px 11px;border:1px solid var(--border);border-radius:9px;font:inherit;background:var(--surface);color:var(--text)">' + opts + '</select>' +
|
||||
'<div style="font-size:.78rem;color:var(--text-3)">Ученики класса получат уведомление со ссылкой. Симуляция будет автоматически опубликована.</div>' +
|
||||
'</div>';
|
||||
var m = LS.modal({ title: 'Раздать классу', content: content, size: 'sm', actions: [
|
||||
{ label: 'Отмена', onClick: function () { m.close(); } },
|
||||
{ label: 'Раздать', primary: true, onClick: function () {
|
||||
var sel = m.body.querySelector('#cs-share-class');
|
||||
var classId = sel ? Number(sel.value) : NaN;
|
||||
LS.customSimShare(dbid, { classId: classId }).then(function (r) {
|
||||
m.close();
|
||||
if (_meta[dbid]) _meta[dbid].status = 'published';
|
||||
renderSection(_catFilter);
|
||||
if (LS.toast) LS.toast('Отправлено ученикам: ' + ((r && r.sent) || 0), 'success');
|
||||
}).catch(function (e) {
|
||||
if (LS.toast) LS.toast((e && e.message) || 'Ошибка раздачи', 'error');
|
||||
});
|
||||
} }
|
||||
] });
|
||||
}).catch(function () {
|
||||
if (LS.toast) LS.toast('Не удалось загрузить классы', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузить список custom-sims, зарегистрировать ленивые манифесты, нарисовать секцию.
|
||||
function init() {
|
||||
if (_initPromise) return _initPromise;
|
||||
@@ -844,6 +942,9 @@ const SIMS = [
|
||||
resolveId: resolveId,
|
||||
renderSection: renderSection,
|
||||
ensureSpec: ensureSpec,
|
||||
del: del
|
||||
del: del,
|
||||
share: shareToClass,
|
||||
clone: clone,
|
||||
setStatus: setStatus
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user