Complete
This commit is contained in:
1 parent
1f3b27516f
commit
a3d3ab8c46
615 files changed
+69080
-85
No files matched your search
@@ -0,0 +1,333 @@
|
||||
let users = []
|
||||
|
||||
// 🔁 Load users from server-side users.json
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res = await fetch("users.json")
|
||||
users = await res.json()
|
||||
} catch (e) {
|
||||
console.error("Failed to load users.json:", e)
|
||||
users = []
|
||||
}
|
||||
}
|
||||
|
||||
// 🔐 Password hashing
|
||||
async function hashPassword(password) {
|
||||
const msgUint8 = new TextEncoder().encode(password)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
|
||||
return Array.from(new Uint8Array(hashBuffer))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
// 👤 Login
|
||||
async function verifyLogin() {
|
||||
const uname = document.getElementById('username').value.trim()
|
||||
const pw = document.getElementById('password').value
|
||||
const hash = await hashPassword(pw)
|
||||
const msg = document.getElementById('loginMessage')
|
||||
|
||||
const user = users.find(u => u.username === uname && u.hash === hash)
|
||||
|
||||
if (user) {
|
||||
localStorage.setItem('loggedInUser', JSON.stringify(user))
|
||||
msg.textContent = ''
|
||||
setupSession(user)
|
||||
await showAdmin()
|
||||
} else {
|
||||
msg.textContent = "Invalid username or password."
|
||||
}
|
||||
}
|
||||
|
||||
// 🧠 On load
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadUsers()
|
||||
displayUserDropdown()
|
||||
|
||||
const savedTheme = localStorage.getItem('theme') || 'playground'
|
||||
document.body.className = savedTheme
|
||||
const dropdown = document.getElementById('themeSelect')
|
||||
if (dropdown) dropdown.value = savedTheme
|
||||
|
||||
const savedUser = localStorage.getItem('loggedInUser')
|
||||
if (savedUser) setupSession(JSON.parse(savedUser))
|
||||
})
|
||||
|
||||
// 🧑🏫 Admin setup
|
||||
async function setupSession(user) {
|
||||
document.getElementById('loginPanel').classList.add('hidden')
|
||||
document.getElementById('nav').classList.remove('hidden')
|
||||
document.getElementById('userInfo').textContent = `${user.username} (${user.role})`
|
||||
|
||||
if (user.role === "admin") {
|
||||
document.getElementById('adminBtn').classList.remove('hidden')
|
||||
await showAdmin()
|
||||
await loadUsers()
|
||||
displayUserDropdown()
|
||||
populateWordUserDropdown()
|
||||
} else {
|
||||
document.getElementById('adminBtn').classList.add('hidden')
|
||||
showStudent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function logoutUser() {
|
||||
localStorage.removeItem('loggedInUser')
|
||||
document.getElementById('loginPanel').classList.remove('hidden')
|
||||
document.getElementById('nav').classList.add('hidden')
|
||||
document.getElementById('adminPanel').classList.add('hidden')
|
||||
document.getElementById('studentPanel').classList.add('hidden')
|
||||
}
|
||||
|
||||
async function showAdmin() {
|
||||
document.getElementById('adminPanel').classList.remove('hidden')
|
||||
document.getElementById('studentPanel').classList.add('hidden')
|
||||
loadWords()
|
||||
displayWordList()
|
||||
await loadUsers()
|
||||
displayUserDropdown()
|
||||
}
|
||||
|
||||
// ➕ Add User
|
||||
async function addNewUser() {
|
||||
const username = document.getElementById("newUsername").value.trim()
|
||||
const password = document.getElementById("newPassword").value
|
||||
const role = document.getElementById("newRole").value
|
||||
const msg = document.getElementById("userAddMessage")
|
||||
|
||||
if (!username || !password) {
|
||||
msg.textContent = "Please fill out all fields."
|
||||
return
|
||||
}
|
||||
|
||||
const hash = await hashPassword(password)
|
||||
await saveUserToServer(username, hash, role)
|
||||
msg.textContent = `✅ User "${username}" added to server.`
|
||||
|
||||
document.getElementById("newUsername").value = ''
|
||||
document.getElementById("newPassword").value = ''
|
||||
await loadUsers()
|
||||
displayUserDropdown()
|
||||
}
|
||||
|
||||
async function saveUserToServer(username, hash, role) {
|
||||
const res = await fetch('/addUser', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, hash, role })
|
||||
})
|
||||
|
||||
const msg = await res.text()
|
||||
alert(msg)
|
||||
}
|
||||
|
||||
// 🗑 User deletion
|
||||
function displayUserDropdown() {
|
||||
const dropdown = document.getElementById("userDropdown")
|
||||
dropdown.innerHTML = `<option value="">-- Select a user --</option>`
|
||||
|
||||
users.forEach(user => {
|
||||
dropdown.innerHTML += `<option value="${user.username}">${user.username} (${user.role})</option>`
|
||||
})
|
||||
}
|
||||
|
||||
function selectUserAction(username) {
|
||||
if (!username) return
|
||||
|
||||
const confirmDelete = confirm(`Are you sure you want to delete user "${username}"?`)
|
||||
if (!confirmDelete) {
|
||||
document.getElementById("userDropdown").value = ""
|
||||
return
|
||||
}
|
||||
|
||||
fetch("/deleteUser", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username })
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(msg => {
|
||||
alert(msg)
|
||||
loadUsers().then(displayUserDropdown)
|
||||
})
|
||||
}
|
||||
|
||||
// 🎨 Theme
|
||||
function changeTheme(themeName) {
|
||||
document.body.className = themeName
|
||||
localStorage.setItem('theme', themeName)
|
||||
}
|
||||
|
||||
// 📚 Word list
|
||||
function loadWords() {
|
||||
const saved = localStorage.getItem('spellingWords')
|
||||
allWords = saved ? JSON.parse(saved) : []
|
||||
}
|
||||
|
||||
function saveWords() {
|
||||
const text = document.getElementById('wordInput').value.trim()
|
||||
allWords = text.split('\n').map(w => w.trim()).filter(Boolean)
|
||||
localStorage.setItem('spellingWords', JSON.stringify(allWords))
|
||||
displayWordList()
|
||||
alert("Word list saved!")
|
||||
}
|
||||
|
||||
function clearWords() {
|
||||
localStorage.removeItem('spellingWords')
|
||||
allWords = []
|
||||
displayWordList()
|
||||
alert("Saved words cleared.")
|
||||
}
|
||||
|
||||
function displayWordList() {
|
||||
const ul = document.getElementById('wordListDisplay')
|
||||
ul.innerHTML = allWords.map(word => `<li>${word}</li>`).join('')
|
||||
}
|
||||
|
||||
function handleFileUpload(event) {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = function(e) {
|
||||
const text = e.target.result
|
||||
const newWords = text.includes(",") ? text.split(",") : text.split("\n")
|
||||
allWords = newWords.map(w => w.trim()).filter(Boolean)
|
||||
localStorage.setItem('spellingWords', JSON.stringify(allWords))
|
||||
displayWordList()
|
||||
alert("Words uploaded and saved!")
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
// 🎮 Spelling Game
|
||||
let allWords = [], words = [], current = 0, results = []
|
||||
|
||||
function showStudent() {
|
||||
document.getElementById('studentPanel').classList.remove('hidden')
|
||||
document.getElementById('adminPanel').classList.add('hidden')
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
loadWords()
|
||||
if (allWords.length === 0) {
|
||||
alert("No words found. Please add words in Admin Panel.")
|
||||
return
|
||||
}
|
||||
|
||||
words = [...allWords]
|
||||
current = 0
|
||||
results = []
|
||||
document.getElementById('summary').classList.add('hidden')
|
||||
showWord()
|
||||
}
|
||||
|
||||
function showWord() {
|
||||
const wordBox = document.getElementById('wordBox')
|
||||
const wordEl = document.getElementById('word')
|
||||
wordBox.classList.remove('hidden')
|
||||
wordEl.textContent = words[current]
|
||||
|
||||
setTimeout(() => {
|
||||
wordBox.classList.add('hidden')
|
||||
document.getElementById('inputSection').classList.remove('hidden')
|
||||
document.getElementById('userInput').value = ''
|
||||
document.getElementById('userInput').focus()
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function submitAnswer() {
|
||||
const input = document.getElementById('userInput').value.trim()
|
||||
results.push({ word: words[current], attempt: input })
|
||||
current++
|
||||
document.getElementById('inputSection').classList.add('hidden')
|
||||
|
||||
if (current < words.length) {
|
||||
showWord()
|
||||
} else {
|
||||
showSummary()
|
||||
}
|
||||
}
|
||||
|
||||
function showSummary() {
|
||||
const summary = document.getElementById('summary')
|
||||
let correct = 0
|
||||
let missed = []
|
||||
|
||||
const report = results.map(({ word, attempt }) => {
|
||||
const isCorrect = word.toLowerCase() === attempt.toLowerCase()
|
||||
if (isCorrect) correct++
|
||||
else missed.push(word)
|
||||
return `<li>${word} — <strong style="color:${isCorrect ? 'green' : 'red'}">${attempt || '(no answer)'}</strong></li>`
|
||||
}).join('')
|
||||
|
||||
summary.innerHTML = `
|
||||
<h2>Results</h2>
|
||||
<p>You got ${correct} out of ${words.length} correct (${Math.round((correct / words.length) * 100)}%)</p>
|
||||
<ul>${report}</ul>
|
||||
${missed.length ? `<button onclick="retryMissed(${JSON.stringify(missed)})">Retry Missed Words</button>` : ''}
|
||||
`
|
||||
summary.classList.remove('hidden')
|
||||
}
|
||||
|
||||
function retryMissed(missedWords) {
|
||||
words = missedWords
|
||||
current = 0
|
||||
results = []
|
||||
document.getElementById('summary').classList.add('hidden')
|
||||
showWord()
|
||||
}
|
||||
async function loadWords() {
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'))
|
||||
if (!user) return allWords = []
|
||||
|
||||
const res = await fetch(`/getWordList?user=${encodeURIComponent(user.username)}`)
|
||||
allWords = await res.json()
|
||||
displayWordList()
|
||||
}
|
||||
async function saveWords() {
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'))
|
||||
if (!user) return
|
||||
|
||||
const text = document.getElementById('wordInput').value.trim()
|
||||
allWords = text.split('\n').map(w => w.trim()).filter(Boolean)
|
||||
|
||||
await fetch('/saveWordList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: user.username, words: allWords })
|
||||
})
|
||||
|
||||
displayWordList()
|
||||
alert("✅ Word list saved to server")
|
||||
}
|
||||
async function clearWords() {
|
||||
const user = JSON.parse(localStorage.getItem('loggedInUser'))
|
||||
if (!user) return
|
||||
|
||||
allWords = []
|
||||
await fetch('/saveWordList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: user.username, words: [] })
|
||||
})
|
||||
|
||||
displayWordList()
|
||||
alert("🗑 Word list cleared")
|
||||
}
|
||||
function populateWordUserDropdown() {
|
||||
const dropdown = document.getElementById("wordUserSelect")
|
||||
dropdown.innerHTML = `<option value="">-- Select a user --</option>`
|
||||
users.forEach(user => {
|
||||
dropdown.innerHTML += `<option value="${user.username}">${user.username}</option>`
|
||||
})
|
||||
}
|
||||
async function loadWordsForSelectedUser() {
|
||||
const selectedUser = document.getElementById("wordUserSelect").value
|
||||
if (!selectedUser) return
|
||||
|
||||
const res = await fetch(`/getWordList?user=${encodeURIComponent(selectedUser)}`)
|
||||
allWords = await res.json()
|
||||
displayWordList()
|
||||
}
|
||||
Reference in new issue
Block a user