diff --git a/bot/static/index.html b/bot/static/index.html index 3c19af0..aa6b000 100644 --- a/bot/static/index.html +++ b/bot/static/index.html @@ -450,6 +450,46 @@ color: #ddd; } + /* Mood Activities Editor */ + .act-mood-row { + margin-bottom: 0.5rem; + border: 1px solid #3a3a3a; + border-radius: 4px; + overflow: hidden; + } + .act-mood-header { + cursor: pointer; + user-select: none; + padding: 0.5rem 0.75rem; + background: #2a2a2a; + display: flex; + align-items: center; + gap: 0.5rem; + } + .act-mood-header:hover { background: #333; } + .act-mood-header .act-mood-name { font-weight: bold; min-width: 120px; } + .act-mood-header .act-mood-stats { color: #888; font-size: 0.8rem; } + .act-mood-content { display: none; padding: 0.75rem; background: #1e1e1e; } + .act-entry { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0; + border-bottom: 1px solid #333; + } + .act-entry:last-child { border-bottom: none; } + .act-entry-icon { font-size: 1.1rem; min-width: 24px; text-align: center; } + .act-entry input[type="text"] { flex: 1; } + .act-entry input[type="number"] { width: 55px; } + .act-entry select { width: 110px; } + .act-toolbar { + display: flex; + gap: 0.5rem; + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid #444; + } + .tab-content { display: none; } @@ -1328,6 +1368,40 @@

       
+
+      
+      
+
+

🎵 Mood Activities

+ +
+ +
@@ -6731,6 +6805,209 @@ function escapeJsonForAttribute(obj) { .replace(/>/g, '>'); } +// ============================================================================ +// MOOD ACTIVITIES EDITOR +// ============================================================================ + +let activitiesData = null; // Full activities data from API +let activitiesOpen = false; // Top-level accordion state +let activitiesSections = { normal: false, evil: false }; // Section accordion state +let activitiesEditing = {}; // Track which moods are in edit mode: { "normal/bubbly": true } +let activitiesEditCache = {}; // Temp storage for edits: { "normal/bubbly": [...] } + +function activitiesToggle() { + activitiesOpen = !activitiesOpen; + document.getElementById('activities-body').style.display = activitiesOpen ? 'block' : 'none'; + document.getElementById('activities-toggle-icon').textContent = activitiesOpen ? '▼' : '▶'; + if (activitiesOpen && !activitiesData) activitiesLoad(); +} + +function activitiesSectionToggle(section) { + activitiesSections[section] = !activitiesSections[section]; + document.getElementById(`activities-${section}-body`).style.display = activitiesSections[section] ? 'block' : 'none'; + document.getElementById(`activities-${section}-icon`).textContent = activitiesSections[section] ? '▼' : '▶'; +} + +async function activitiesLoad() { + const statusEl = document.getElementById('activities-status'); + statusEl.textContent = 'Loading...'; + try { + activitiesData = await apiCall('/activities'); + const normalMoods = Object.keys(activitiesData.normal || {}); + const evilMoods = Object.keys(activitiesData.evil || {}); + const total = normalMoods.length + evilMoods.length; + document.getElementById('activities-summary').textContent = `(${total} moods configured)`; + activitiesRenderSection('normal'); + activitiesRenderSection('evil'); + statusEl.textContent = ''; + } catch (e) { + statusEl.textContent = 'Failed to load: ' + e.message; + statusEl.style.color = '#e74c3c'; + } +} + +function activitiesRenderSection(section) { + const container = document.getElementById(`activities-${section}-list`); + if (!activitiesData || !activitiesData[section]) { container.innerHTML = '

No data

'; return; } + + const moods = activitiesData[section]; + let html = ''; + for (const [mood, entries] of Object.entries(moods)) { + const key = `${section}/${mood}`; + const isEditing = activitiesEditing[key]; + const songs = entries.filter(e => e.type === 'listening').length; + const games = entries.filter(e => e.type === 'playing').length; + + html += `
`; + html += `
`; + html += ` ${mood}`; + html += `${songs}🎵 ${games}🎮`; + html += `
`; + html += `
`; + + if (isEditing) { + html += activitiesRenderEditForm(section, mood, activitiesEditCache[key] || entries); + } else { + html += activitiesRenderView(section, mood, entries); + } + + html += `
`; + } + container.innerHTML = html; +} + +function activitiesRenderView(section, mood, entries) { + let html = ''; + for (const entry of entries) { + const icon = entry.type === 'listening' ? '🎵' : '🎮'; + const label = entry.type === 'listening' ? 'Listening to' : 'Playing'; + html += `
`; + html += `${icon}`; + html += `${escapeHtml(entry.name)}`; + html += `weight: ${entry.weight}`; + html += `
`; + } + html += `
`; + html += ``; + html += `
`; + return html; +} + +function activitiesRenderEditForm(section, mood, entries) { + let html = ''; + for (let i = 0; i < entries.length; i++) { + const e = entries[i]; + html += `
`; + html += ``; + html += ``; + html += ``; + html += ``; + html += `
`; + } + html += `
`; + html += ``; + html += ``; + html += ``; + html += `
`; + return html; +} + +function activitiesMoodToggle(section, mood) { + const el = document.getElementById(`act-content-${section}-${mood}`); + const iconEl = document.getElementById(`act-icon-${section}-${mood}`); + if (!el) return; + const isOpen = el.style.display === 'block'; + el.style.display = isOpen ? 'none' : 'block'; + if (iconEl) iconEl.textContent = isOpen ? '▶' : '▼'; +} + +function activitiesStartEdit(section, mood) { + const key = `${section}/${mood}`; + const entries = activitiesData[section][mood]; + // Deep clone entries for editing + activitiesEditCache[key] = JSON.parse(JSON.stringify(entries)); + activitiesEditing[key] = true; + activitiesRenderSection(section); + // Auto-expand the mood panel + const el = document.getElementById(`act-content-${section}-${mood}`); + const iconEl = document.getElementById(`act-icon-${section}-${mood}`); + if (el) el.style.display = 'block'; + if (iconEl) iconEl.textContent = '▼'; +} + +function activitiesCancelEdit(section, mood) { + const key = `${section}/${mood}`; + delete activitiesEditing[key]; + delete activitiesEditCache[key]; + activitiesRenderSection(section); +} + +function activitiesAddEntry(section, mood) { + const key = `${section}/${mood}`; + // First, sync current form values to cache + activitiesSyncFormToCache(section, mood); + activitiesEditCache[key].push({ type: 'listening', name: '', weight: 1 }); + activitiesRenderSection(section); + // Keep the mood panel open + const el = document.getElementById(`act-content-${section}-${mood}`); + if (el) el.style.display = 'block'; +} + +function activitiesRemoveEntry(section, mood, index) { + const key = `${section}/${mood}`; + activitiesSyncFormToCache(section, mood); + activitiesEditCache[key].splice(index, 1); + activitiesRenderSection(section); + const el = document.getElementById(`act-content-${section}-${mood}`); + if (el) el.style.display = 'block'; +} + +function activitiesSyncFormToCache(section, mood) { + const key = `${section}/${mood}`; + const entries = activitiesEditCache[key] || []; + for (let i = 0; i < entries.length; i++) { + const typeEl = document.getElementById(`act-type-${section}-${mood}-${i}`); + const nameEl = document.getElementById(`act-name-${section}-${mood}-${i}`); + const weightEl = document.getElementById(`act-weight-${section}-${mood}-${i}`); + if (typeEl) entries[i].type = typeEl.value; + if (nameEl) entries[i].name = nameEl.value; + if (weightEl) entries[i].weight = parseInt(weightEl.value) || 1; + } + activitiesEditCache[key] = entries; +} + +async function activitiesSave(section, mood) { + const key = `${section}/${mood}`; + activitiesSyncFormToCache(section, mood); + const entries = activitiesEditCache[key]; + + // Client-side validation + for (let i = 0; i < entries.length; i++) { + if (!entries[i].name || !entries[i].name.trim()) { + showNotification(`Entry ${i + 1}: name cannot be empty`, 'error'); + return; + } + if (!entries[i].weight || entries[i].weight < 1) { + showNotification(`Entry ${i + 1}: weight must be at least 1`, 'error'); + return; + } + } + + try { + await apiCall(`/activities/${section}/${mood}`, 'POST', { activities: entries }); + showNotification(`Saved activities for ${section}/${mood}`, 'success'); + delete activitiesEditing[key]; + delete activitiesEditCache[key]; + // Reload to get fresh data + await activitiesLoad(); + } catch (e) { + showNotification('Save failed: ' + e.message, 'error'); + } +} +