Files
SMF/app/js/flashcards.js

204 lines
6.4 KiB
JavaScript
Raw Normal View History

// =====================================================
// Flashcards — flippable cards with category filter
// =====================================================
let fcData = null;
let fcState = {
filter: 'all',
index: 0,
flipped: false,
cards: [],
known: new Set(), // localStorage-backed
hard: new Set()
};
async function fcLoad() {
if (fcData) return fcData;
try {
const res = await fetch('data/flashcards.json');
fcData = await res.json();
} catch (e) {
console.error('Klarte ikke laste flashcards:', e);
fcData = [];
}
// Load saved state
try {
const saved = JSON.parse(localStorage.getItem('smf-fc-state') || '{}');
if (saved.known) fcState.known = new Set(saved.known);
if (saved.hard) fcState.hard = new Set(saved.hard);
} catch {}
return fcData;
}
function fcSaveState() {
localStorage.setItem('smf-fc-state', JSON.stringify({
known: [...fcState.known],
hard: [...fcState.hard]
}));
}
function fcFilterCards(filter) {
if (filter === 'all') return fcData;
if (filter === 'hard') return fcData.filter(c => fcState.hard.has(c.id));
if (filter === 'new') return fcData.filter(c => !fcState.known.has(c.id) && !fcState.hard.has(c.id));
return fcData.filter(c => c.category === filter);
}
function fcSetFilter(filter) {
fcState.filter = filter;
fcState.cards = fcFilterCards(filter);
// shuffle once when filter changes
fcShuffle(fcState.cards);
fcState.index = 0;
fcState.flipped = false;
fcRender();
}
function fcShuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
function fcRender() {
const total = fcState.cards.length;
document.getElementById('fcCurrent').textContent = total > 0 ? (fcState.index + 1) : 0;
document.getElementById('fcTotal').textContent = total;
document.getElementById('fcKnown').textContent = fcState.known.size;
const card = document.getElementById('fcCard');
if (total === 0) {
document.getElementById('fcFront').textContent = 'Ingen kort i denne kategorien';
document.getElementById('fcBack').textContent = 'Prøv et annet filter';
document.getElementById('fcCategory').textContent = '—';
card.classList.remove('fc-card--flipped');
return;
}
const c = fcState.cards[fcState.index];
document.getElementById('fcFront').textContent = c.front;
document.getElementById('fcBack').innerHTML = c.back;
document.getElementById('fcCategory').textContent = (c.subcategory || categoryLabel(c.category));
card.classList.toggle('fc-card--flipped', fcState.flipped);
}
function categoryLabel(cat) {
return {
fasttrack: 'Fast-track',
etikk: 'Etikk',
baerekraft: 'Bærekraft',
samfunn: 'Samfunnsansvar',
verktoy: 'Verktøy',
case: 'Case'
}[cat] || cat;
}
function fcRenderFilters() {
const container = document.getElementById('fcFilters');
if (!container) return;
const filters = [
{ id: 'all', label: 'Alle' },
{ id: 'fasttrack', label: '» Fast-track' },
{ id: 'new', label: 'Nye' },
{ id: 'hard', label: 'Glemt' },
{ id: 'etikk', label: 'Etikk' },
{ id: 'baerekraft', label: 'Bærekraft' },
{ id: 'samfunn', label: 'Samfunnsansvar' },
{ id: 'verktoy', label: 'Verktøy' },
{ id: 'case', label: 'Case' }
];
container.innerHTML = filters.map(f => {
const active = fcState.filter === f.id ? 'fc-filter--active' : '';
return `<button class="fc-filter ${active}" data-filter="${f.id}">${f.label}</button>`;
}).join('');
}
function fcNext() {
if (!fcState.cards.length) return;
fcState.flipped = false;
fcState.index = (fcState.index + 1) % fcState.cards.length;
fcRender();
}
function fcPrev() {
if (!fcState.cards.length) return;
fcState.flipped = false;
fcState.index = (fcState.index - 1 + fcState.cards.length) % fcState.cards.length;
fcRender();
}
function fcMark(level) {
if (!fcState.cards.length) return;
const c = fcState.cards[fcState.index];
if (level === 'hard') {
fcState.hard.add(c.id);
fcState.known.delete(c.id);
} else if (level === 'good') {
fcState.hard.delete(c.id);
} else if (level === 'easy') {
fcState.known.add(c.id);
fcState.hard.delete(c.id);
}
fcSaveState();
fcNext();
}
async function fcInit(initialFilter) {
await fcLoad();
// Dyplenke, f.eks. #/flashcards/fasttrack — sett startfilter hvis gyldig
if (initialFilter) {
const valid = ['all', 'fasttrack', 'new', 'hard', 'etikk', 'baerekraft', 'samfunn', 'verktoy', 'case'];
if (valid.includes(initialFilter)) fcState.filter = initialFilter;
}
fcState.cards = fcFilterCards(fcState.filter);
fcShuffle(fcState.cards);
fcState.index = 0;
fcState.flipped = false;
fcRenderFilters();
fcRender();
// Card click → flip
const card = document.getElementById('fcCard');
card.addEventListener('click', () => {
fcState.flipped = !fcState.flipped;
fcRender();
});
document.getElementById('fcNext').addEventListener('click', (e) => { e.stopPropagation(); fcNext(); });
document.getElementById('fcPrev').addEventListener('click', (e) => { e.stopPropagation(); fcPrev(); });
document.getElementById('fcHard').addEventListener('click', (e) => { e.stopPropagation(); fcMark('hard'); });
document.getElementById('fcGood').addEventListener('click', (e) => { e.stopPropagation(); fcMark('good'); });
document.getElementById('fcEasy').addEventListener('click', (e) => { e.stopPropagation(); fcMark('easy'); });
document.getElementById('fcFilters').addEventListener('click', (e) => {
const btn = e.target.closest('[data-filter]');
if (btn) {
fcSetFilter(btn.dataset.filter);
fcRenderFilters();
}
});
// Keyboard
const handler = (e) => {
if (!location.hash.startsWith('#/flashcards')) {
document.removeEventListener('keydown', handler);
return;
}
const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); fcState.flipped = !fcState.flipped; fcRender(); }
else if (e.key === 'ArrowRight') { e.preventDefault(); fcNext(); }
else if (e.key === 'ArrowLeft') { e.preventDefault(); fcPrev(); }
else if (e.key === '1') { fcMark('hard'); }
else if (e.key === '2') { fcMark('good'); }
else if (e.key === '3') { fcMark('easy'); }
};
document.addEventListener('keydown', handler);
}
SMF.fcInit = fcInit;