feat: скрывать отключённые/недоступные модули с galaxy map по feature flags
This commit is contained in:
+81
-40
@@ -335,29 +335,88 @@ const CAT_INFO = {
|
||||
personal: { label:'Личное', rgb:[255,179,71] },
|
||||
};
|
||||
|
||||
/* ══ Build HTML nodes ══ */
|
||||
/* ══ Feature flag → module ID mapping ══ */
|
||||
const FEAT_MAP = {
|
||||
hangman: ['hangman'],
|
||||
crossword: ['crossword'],
|
||||
pet: ['pet'],
|
||||
red_book: ['redbook'],
|
||||
collection: ['collection'],
|
||||
lab: ['lab'],
|
||||
knowledge_map: ['knowledge'],
|
||||
board: ['board'],
|
||||
biochem: ['biochem'],
|
||||
};
|
||||
const NO_CLASS_IDS = new Set(['board','lab','hangman','crossword','pet','collection','knowledge','redbook']);
|
||||
|
||||
/* activeModules — updated after features load; canvas uses this for connections */
|
||||
let activeModules = MODULES;
|
||||
|
||||
/* ══ Build HTML nodes (called after features are known) ══ */
|
||||
const nodesWrap = document.getElementById('gx-nodes');
|
||||
const nodeMap = {};
|
||||
|
||||
MODULES.forEach((m, i) => {
|
||||
const [r,g,b] = CAT_INFO[m.cat].rgb;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'gx-node' + (m.id === 'dashboard' ? ' gx-main' : '');
|
||||
el.dataset.id = m.id;
|
||||
el.dataset.cat = m.cat;
|
||||
el.style.cssText = `left:${m.x*100}%;top:${m.y*100}%;--nc:${r},${g},${b};animation-delay:${i*0.045}s`;
|
||||
el.innerHTML = `
|
||||
<div class="gx-orb"><i data-lucide="${m.icon}"></i></div>
|
||||
<span class="gx-label">${m.label}</span>`;
|
||||
el.addEventListener('mouseenter', () => showTip(m, el));
|
||||
el.addEventListener('mouseleave', queueHideTip);
|
||||
el.addEventListener('click', () => location.href = m.href);
|
||||
nodesWrap.appendChild(el);
|
||||
nodeMap[m.id] = el;
|
||||
});
|
||||
function buildNodes(mods) {
|
||||
nodesWrap.innerHTML = '';
|
||||
Object.keys(nodeMap).forEach(k => delete nodeMap[k]);
|
||||
mods.forEach((m, i) => {
|
||||
const [r,g,b] = CAT_INFO[m.cat].rgb;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'gx-node' + (m.id === 'dashboard' ? ' gx-main' : '');
|
||||
el.dataset.id = m.id;
|
||||
el.dataset.cat = m.cat;
|
||||
el.style.cssText = `left:${m.x*100}%;top:${m.y*100}%;--nc:${r},${g},${b};animation-delay:${i*0.045}s`;
|
||||
el.innerHTML = `
|
||||
<div class="gx-orb"><i data-lucide="${m.icon}"></i></div>
|
||||
<span class="gx-label">${m.label}</span>`;
|
||||
el.addEventListener('mouseenter', () => showTip(m, el));
|
||||
el.addEventListener('mouseleave', queueHideTip);
|
||||
el.addEventListener('click', () => location.href = m.href);
|
||||
nodesWrap.appendChild(el);
|
||||
nodeMap[m.id] = el;
|
||||
});
|
||||
if (window.lucide) lucide.createIcons();
|
||||
document.getElementById('gx-count').textContent = mods.length + ' модулей';
|
||||
}
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
document.getElementById('gx-count').textContent = MODULES.length + ' модулей';
|
||||
/* ══ Mobile fallback (called after features are known) ══ */
|
||||
function buildMobile(mods) {
|
||||
const mobEl = document.getElementById('gx-mobile');
|
||||
mobEl.innerHTML = '';
|
||||
const byCat = {};
|
||||
mods.forEach(m => { (byCat[m.cat] = byCat[m.cat]||[]).push(m); });
|
||||
['study','practice','games','personal'].forEach(cat => {
|
||||
if (!byCat[cat]?.length) return;
|
||||
const info = CAT_INFO[cat];
|
||||
const [r,g,b] = info.rgb;
|
||||
let html = `<div class="gx-mob-sec-title"><span class="gx-mob-dot" style="background:rgb(${r},${g},${b})"></span>${info.label}</div><div class="gx-mob-grid">`;
|
||||
byCat[cat].forEach(m => {
|
||||
html += `<a href="${m.href}" class="gx-mob-card" data-cat="${cat}" style="--nc:${r},${g},${b}">
|
||||
<div class="gx-mob-icon"><i data-lucide="${m.icon}"></i></div>
|
||||
<div><div class="gx-mob-name">${m.label}</div><div class="gx-mob-cat">${info.label}</div></div>
|
||||
</a>`;
|
||||
});
|
||||
html += '</div>';
|
||||
mobEl.insertAdjacentHTML('beforeend', html);
|
||||
});
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
/* ══ Async init: load features, filter modules, render ══ */
|
||||
(async () => {
|
||||
let disabled = new Set();
|
||||
try {
|
||||
const feats = await LS.loadFeatures();
|
||||
for (const [key, ids] of Object.entries(FEAT_MAP)) {
|
||||
if (feats[key] === false) ids.forEach(id => disabled.add(id));
|
||||
}
|
||||
if (feats._no_class) NO_CLASS_IDS.forEach(id => disabled.add(id));
|
||||
} catch { /* если не залогинен — показываем всё */ }
|
||||
|
||||
activeModules = disabled.size ? MODULES.filter(m => !disabled.has(m.id)) : MODULES;
|
||||
buildNodes(activeModules);
|
||||
buildMobile(activeModules);
|
||||
})();
|
||||
|
||||
/* ══ Tooltip ══ */
|
||||
const tt = document.getElementById('gx-tt');
|
||||
@@ -479,12 +538,12 @@ function drawFrame() {
|
||||
});
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
// Connection lines
|
||||
// Connection lines (only between visible modules)
|
||||
ctx.save();
|
||||
ctx.setLineDash([3, 10]);
|
||||
CONNECTIONS.forEach(([a, b]) => {
|
||||
const ma = MODULES.find(m => m.id === a);
|
||||
const mb = MODULES.find(m => m.id === b);
|
||||
const ma = activeModules.find(m => m.id === a);
|
||||
const mb = activeModules.find(m => m.id === b);
|
||||
if (!ma || !mb) return;
|
||||
const isHovered = hoveredId && (a === hoveredId || b === hoveredId);
|
||||
ctx.strokeStyle = isHovered ? 'rgba(255,255,255,0.38)' : 'rgba(255,255,255,0.065)';
|
||||
@@ -513,24 +572,6 @@ document.querySelectorAll('.gx-fil').forEach(btn => {
|
||||
});
|
||||
});
|
||||
|
||||
/* ══ Mobile fallback: card grid ══ */
|
||||
const mobEl = document.getElementById('gx-mobile');
|
||||
const byCat = {};
|
||||
MODULES.forEach(m => { (byCat[m.cat] = byCat[m.cat]||[]).push(m); });
|
||||
['study','practice','games','personal'].forEach(cat => {
|
||||
const info = CAT_INFO[cat];
|
||||
const [r,g,b] = info.rgb;
|
||||
let html = `<div class="gx-mob-sec-title"><span class="gx-mob-dot" style="background:rgb(${r},${g},${b})"></span>${info.label}</div><div class="gx-mob-grid">`;
|
||||
(byCat[cat]||[]).forEach(m => {
|
||||
html += `<a href="${m.href}" class="gx-mob-card" data-cat="${cat}" style="--nc:${r},${g},${b}">
|
||||
<div class="gx-mob-icon"><i data-lucide="${m.icon}"></i></div>
|
||||
<div><div class="gx-mob-name">${m.label}</div><div class="gx-mob-cat">${info.label}</div></div>
|
||||
</a>`;
|
||||
});
|
||||
html += '</div>';
|
||||
mobEl.insertAdjacentHTML('beforeend', html);
|
||||
});
|
||||
if (window.lucide) lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user