feat(admin): тумблер фичи для «Квантик» (паритет с другими играми)

У Квантика не было фиче-флага — его нельзя было выключить, и он всегда висел
в сайдбаре (даже у учеников без класса). Добавлено по образцу остальных игр:
- adminController.updateFeatures: 'quantik' в whitelist (PATCH принимает флаг).
- games.js: пункт «Квантик: Законы Мира» в GAME_FEATURES и FS_FEATURES
  (тумблер в админке → Игры; пишет feature_quantik_enabled).
- api.js hideDisabledFeatures: quantik -> ['/quantik','/quantik.html'] (скрытие
  из сайдбара при выключении) + '/quantik' в classOnlyHrefs/classOnlyPaths
  (скрыт у учеников без класса, как прочие игры).

Миграция не нужна: флаг «неявно включён», пока админ не выключит (features[key]
!== false => включено). Требует Ctrl+F5 (фронт).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-15 16:00:23 +03:00
parent 56fc15418e
commit 477d47e9e6
3 changed files with 6 additions and 3 deletions
+1 -1
View File
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
function updateFeatures(req, res) { function updateFeatures(req, res) {
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection', const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom', 'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
'gamification', 'assistant', 'sim_builder']; 'gamification', 'assistant', 'sim_builder', 'quantik'];
const updates = req.body; const updates = req.body;
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)"); const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?"); const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
+2
View File
@@ -18,6 +18,7 @@
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' }, { key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' }, { key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
{ key: 'sim_builder', label: 'Конструктор симуляций', desc: 'Создание учителем своих интерактивных симуляций (рабочее поле, формулы, физика, графики)', icon: 'pencil-ruler' }, { key: 'sim_builder', label: 'Конструктор симуляций', desc: 'Создание учителем своих интерактивных симуляций (рабочее поле, формулы, физика, графики)', icon: 'pencil-ruler' },
{ key: 'quantik', label: 'Квантик: Законы Мира', desc: '2D физика-головоломка: уровни на движке симуляций, прогресс, скины', icon: 'rocket' },
]; ];
const FS_FEATURES = [ const FS_FEATURES = [
@@ -28,6 +29,7 @@
{ key: 'red_book', label: 'Красная книга', desc: 'Интерактивная Красная книга РБ: виды, биомы, квесты', icon: 'leaf' }, { key: 'red_book', label: 'Красная книга', desc: 'Интерактивная Красная книга РБ: виды, биомы, квесты', icon: 'leaf' },
{ key: 'collection', label: 'Коллекция', desc: 'Коллекция карточек и игровой прогресс ученика', icon: 'layers' }, { key: 'collection', label: 'Коллекция', desc: 'Коллекция карточек и игровой прогресс ученика', icon: 'layers' },
{ key: 'lab', label: 'Лаборатория', desc: 'Виртуальные симуляции и интерактивные опыты', icon: 'flask-conical' }, { key: 'lab', label: 'Лаборатория', desc: 'Виртуальные симуляции и интерактивные опыты', icon: 'flask-conical' },
{ key: 'quantik', label: 'Квантик: Законы Мира', desc: '2D физика-головоломка на движке симуляций', icon: 'rocket' },
{ key: 'knowledge_map',label: 'Карта знаний', desc: 'Визуальная карта тем и связей между понятиями', icon: 'map' }, { key: 'knowledge_map',label: 'Карта знаний', desc: 'Визуальная карта тем и связей между понятиями', icon: 'map' },
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для повторения терминов и понятий', icon: 'square-stack' }, { key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для повторения терминов и понятий', icon: 'square-stack' },
{ key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце', icon: 'image' }, { key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце', icon: 'image' },
+3 -2
View File
@@ -863,6 +863,7 @@ async function hideDisabledFeatures() {
sim_builder: ['/sim-builder', '/sim-builder.html'], sim_builder: ['/sim-builder', '/sim-builder.html'],
exam9: ['/exam9', '/exam9.html'], exam9: ['/exam9', '/exam9.html'],
textbooks: ['/textbooks', '/textbooks.html', '/textbook'], textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
quantik: ['/quantik', '/quantik.html'],
}; };
for (const [key, hrefs] of Object.entries(map)) { for (const [key, hrefs] of Object.entries(map)) {
if (feats[key] === false) { if (feats[key] === false) {
@@ -908,7 +909,7 @@ async function hideDisabledFeatures() {
'/board', '/lab', '/hangman', '/crossword', '/pet', '/board', '/lab', '/hangman', '/crossword', '/pet',
'/collection', '/collection.html', '/knowledge-map', '/collection', '/collection.html', '/knowledge-map',
'/red-book', '/red-book.html', '/red-book-ecosystem.html', '/red-book-biomes.html', '/red-book', '/red-book.html', '/red-book-ecosystem.html', '/red-book-biomes.html',
'/flashcards', '/live-quiz', '/flashcards', '/live-quiz', '/quantik',
]; ];
classOnlyHrefs.forEach(href => { classOnlyHrefs.forEach(href => {
document.querySelectorAll(`[href="${href}"]`).forEach(el => el.style.display = 'none'); document.querySelectorAll(`[href="${href}"]`).forEach(el => el.style.display = 'none');
@@ -919,7 +920,7 @@ async function hideDisabledFeatures() {
'/board', '/lab', '/hangman', '/crossword', '/pet', '/board', '/lab', '/hangman', '/crossword', '/pet',
'/collection', '/collection-rb', '/knowledge-map', '/collection', '/collection-rb', '/knowledge-map',
'/red-book', '/red-book-ecosystem', '/red-book-biomes', '/red-book-games', '/red-book', '/red-book-ecosystem', '/red-book-biomes', '/red-book-games',
'/flashcards', '/live-quiz', '/flashcards', '/live-quiz', '/quantik',
]; ];
if (classOnlyPaths.some(h => cur === h || cur === h + '.html')) { if (classOnlyPaths.some(h => cur === h || cur === h + '.html')) {
window.location.href = '/dashboard'; window.location.href = '/dashboard';