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:
@@ -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 = ?");
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user