Release v1.0.2: Fix startup syntax error, offline mode, UI improvements
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { authAPI, moodAPI, thoughtAPI, gratitudeAPI, progressAPI, notificationAPI, exerciseAPI, isAuthenticated, initializeAPI } from './api.js';
|
||||
import { authAPI, moodAPI, thoughtAPI, gratitudeAPI, progressAPI, notificationAPI, exerciseAPI, isAuthenticated, initializeAPI } from './offline-api.js';
|
||||
|
||||
// Sound Manager using Web Audio API
|
||||
class SoundManager {
|
||||
@@ -69,41 +69,57 @@ const soundManager = new SoundManager();
|
||||
|
||||
// Initialize app when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
// Check authentication
|
||||
if (!isAuthenticated()) {
|
||||
showLoginModal();
|
||||
return;
|
||||
try {
|
||||
console.log('App initialization started');
|
||||
|
||||
// Check authentication
|
||||
if (!isAuthenticated()) {
|
||||
console.log('User not authenticated, showing login modal');
|
||||
showLoginModal();
|
||||
// Hide initial loader if login modal is shown
|
||||
const loader = document.getElementById('initial-loader');
|
||||
if (loader) loader.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize API
|
||||
await initializeAPI();
|
||||
|
||||
// Create floating particles
|
||||
createParticles();
|
||||
|
||||
// Add fade-in animation to cards
|
||||
document.querySelectorAll('.card').forEach((card, index) => {
|
||||
card.style.animationDelay = `${index * 0.1}s`;
|
||||
card.classList.add('fade-in');
|
||||
});
|
||||
|
||||
// Add motivational quotes rotation
|
||||
startQuoteRotation();
|
||||
|
||||
// Initialize emotion sliders
|
||||
initializeEmotionSliders();
|
||||
|
||||
// Initialize belief rating slider
|
||||
initializeBeliefSlider();
|
||||
|
||||
// Load saved data from API
|
||||
await loadSavedData();
|
||||
|
||||
// Render Home Page initially
|
||||
showSection('home');
|
||||
|
||||
// Initialize inactivity tracker
|
||||
initInactivityTracker();
|
||||
|
||||
console.log('App initialization complete');
|
||||
} catch (error) {
|
||||
console.error('Initialization error:', error);
|
||||
const loader = document.getElementById('initial-loader');
|
||||
if (loader) {
|
||||
loader.innerHTML = `<div style="color: red; padding: 20px;"><h3>Init Error</h3><p>${error.message}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize API
|
||||
await initializeAPI();
|
||||
|
||||
// Create floating particles
|
||||
createParticles();
|
||||
|
||||
// Add fade-in animation to cards
|
||||
document.querySelectorAll('.card').forEach((card, index) => {
|
||||
card.style.animationDelay = `${index * 0.1}s`;
|
||||
card.classList.add('fade-in');
|
||||
});
|
||||
|
||||
// Add motivational quotes rotation
|
||||
startQuoteRotation();
|
||||
|
||||
// Initialize emotion sliders
|
||||
initializeEmotionSliders();
|
||||
|
||||
// Initialize belief rating slider
|
||||
initializeBeliefSlider();
|
||||
|
||||
// Load saved data from API
|
||||
await loadSavedData();
|
||||
|
||||
// Render Home Page initially
|
||||
showSection('home');
|
||||
|
||||
// Initialize inactivity tracker
|
||||
initInactivityTracker();
|
||||
});
|
||||
|
||||
// Inactivity Tracker
|
||||
@@ -287,7 +303,8 @@ async function saveMoodEntry() {
|
||||
intensity = slider ? slider.value : '5';
|
||||
}
|
||||
|
||||
const notes = document.getElementById('moodNotes')?.value;
|
||||
const notesInput = document.getElementById('moodNotes');
|
||||
const notes = notesInput ? notesInput.value : '';
|
||||
|
||||
if (!moodType) {
|
||||
showToast('Please select a mood', 'warning');
|
||||
@@ -325,10 +342,10 @@ async function saveMoodEntry() {
|
||||
|
||||
// Thought record with API
|
||||
async function saveThoughtRecord() {
|
||||
const situation = document.getElementById('situation')?.value;
|
||||
const thoughts = document.getElementById('thoughts')?.value;
|
||||
const evidence = document.getElementById('evidence')?.value;
|
||||
const alternative = document.getElementById('alternative')?.value;
|
||||
const situation = document.getElementById('situation') ? document.getElementById('situation').value : '';
|
||||
const thoughts = document.getElementById('thoughts') ? document.getElementById('thoughts').value : '';
|
||||
const evidence = document.getElementById('evidence') ? document.getElementById('evidence').value : '';
|
||||
const alternative = document.getElementById('alternative') ? document.getElementById('alternative').value : '';
|
||||
|
||||
if (!situation || !thoughts) {
|
||||
showToast('Please fill in at least the situation and thoughts', 'warning');
|
||||
@@ -338,8 +355,10 @@ async function saveThoughtRecord() {
|
||||
// Get emotions
|
||||
const emotions = [];
|
||||
document.querySelectorAll('.emotion-inputs').forEach(input => {
|
||||
const name = input.querySelector('.emotion-name')?.value;
|
||||
const value = input.querySelector('.emotion-slider')?.value;
|
||||
const nameInput = input.querySelector('.emotion-name');
|
||||
const valueInput = input.querySelector('.emotion-slider');
|
||||
const name = nameInput ? nameInput.value : '';
|
||||
const value = valueInput ? valueInput.value : '';
|
||||
if (name && value) {
|
||||
emotions.push({ name, intensity: parseInt(value) });
|
||||
}
|
||||
@@ -412,37 +431,75 @@ async function loadSavedData() {
|
||||
|
||||
// Progress with API
|
||||
async function updateProgress() {
|
||||
console.log('updateProgress: Starting update...');
|
||||
try {
|
||||
const stats = await progressAPI.getProgressStats();
|
||||
const history = await progressAPI.getProgressHistory();
|
||||
// Add timeout to prevent infinite loading
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout loading stats')), 5000)
|
||||
);
|
||||
|
||||
const statsPromise = progressAPI.getProgressStats();
|
||||
const historyPromise = progressAPI.getProgressHistory();
|
||||
|
||||
const [stats, history] = await Promise.race([
|
||||
Promise.all([statsPromise, historyPromise]),
|
||||
timeoutPromise
|
||||
]);
|
||||
|
||||
// Update progress stats
|
||||
const progressContainer = document.getElementById('progress-stats');
|
||||
if (progressContainer) {
|
||||
progressContainer.innerHTML = `
|
||||
console.log('updateProgress: Data received', stats);
|
||||
|
||||
const statsHTML = `
|
||||
<div class="progress-card">
|
||||
<div class="progress-value">${stats.today.mood_score || '-'}</div>
|
||||
<div class="progress-value">${(stats.today && stats.today.mood_score) ? stats.today.mood_score : '-'}</div>
|
||||
<div class="progress-label">Today's Mood</div>
|
||||
</div>
|
||||
<div class="progress-card">
|
||||
<div class="progress-value">${stats.totals.totalSessions || 0}</div>
|
||||
<div class="progress-value">${(stats.totals && stats.totals.totalSessions) ? stats.totals.totalSessions : 0}</div>
|
||||
<div class="progress-label">Sessions</div>
|
||||
</div>
|
||||
<div class="progress-card">
|
||||
<div class="progress-value">${Math.round(stats.week.avgMood * 10) / 10 || '-'}</div>
|
||||
<div class="progress-value">${(stats.week && stats.week.avgMood) ? (Math.round(stats.week.avgMood * 10) / 10) : '-'}</div>
|
||||
<div class="progress-label">Weekly Avg</div>
|
||||
</div>
|
||||
<div class="progress-card">
|
||||
<div class="progress-value">${stats.totals.totalGratitude || 0}</div>
|
||||
<div class="progress-value">${(stats.totals && stats.totals.totalGratitude) ? stats.totals.totalGratitude : 0}</div>
|
||||
<div class="progress-label">Gratitude</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
// Update progress page stats
|
||||
const progressContainer = document.getElementById('progress-stats');
|
||||
if (progressContainer) {
|
||||
progressContainer.innerHTML = statsHTML;
|
||||
}
|
||||
|
||||
// Update home page stats
|
||||
const homeStatsContainer = document.getElementById('home-stats-container');
|
||||
if (homeStatsContainer) {
|
||||
homeStatsContainer.innerHTML = `<div class="progress-container">${statsHTML}</div>`;
|
||||
}
|
||||
|
||||
// Draw weekly chart
|
||||
drawWeeklyChart(history);
|
||||
console.log('updateProgress: Update complete');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to update progress:', error);
|
||||
|
||||
// Fallback UI to remove spinner
|
||||
const errorHTML = `
|
||||
<div class="progress-container">
|
||||
<div class="progress-card" onclick="updateProgress()">
|
||||
<div class="progress-value">⚠️</div>
|
||||
<div class="progress-label">Tap to Retry</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const homeStatsContainer = document.getElementById('home-stats-container');
|
||||
if (homeStatsContainer) {
|
||||
homeStatsContainer.innerHTML = errorHTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,6 +850,7 @@ async function renderHistory(type) {
|
||||
const container = document.getElementById('history-container');
|
||||
if (!container) return;
|
||||
|
||||
// Clear existing content
|
||||
container.innerHTML = '<div class="spinner"></div>';
|
||||
|
||||
try {
|
||||
@@ -1190,24 +1248,6 @@ function startGratitude() {
|
||||
document.getElementById('exercises').classList.add('blur-background');
|
||||
}
|
||||
|
||||
function startBreathing() {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'exercise-modal';
|
||||
modal.style.display = 'block';
|
||||
modal.innerHTML = `
|
||||
<div class="card breathing-content">
|
||||
<h2 class="card-title">Breathe With Me 🌬️</h2>
|
||||
<div id="breathing-circle" class="breathing-circle inhale">
|
||||
<span id="breathing-text" class="breathing-text">Breathe In</span>
|
||||
</div>
|
||||
<p class="breathing-instructions">Follow the rhythm of the circle and the sound cues.</p>
|
||||
<button class="btn btn-secondary" onclick="stopBreathingExercise(); this.closest('.exercise-modal').remove()">Finish</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
startBreathingExercise();
|
||||
}
|
||||
|
||||
function closeExercise(exerciseId) {
|
||||
document.getElementById(exerciseId).style.display = 'none';
|
||||
document.getElementById('exercises').classList.remove('blur-background');
|
||||
@@ -1236,53 +1276,177 @@ function addGratitudeInput() {
|
||||
gratitudeContainer.appendChild(newInput);
|
||||
}
|
||||
|
||||
// Breathing exercise functions
|
||||
let breathingInterval;
|
||||
let breathingPhase = 'inhale';
|
||||
// --- Smart Breathing System ---
|
||||
let breathingState = {
|
||||
isActive: false,
|
||||
technique: 'balance',
|
||||
timer: null
|
||||
};
|
||||
|
||||
function startBreathingExercise() {
|
||||
// Initial sound
|
||||
soundManager.playBreathIn();
|
||||
|
||||
breathingInterval = setInterval(() => {
|
||||
const circle = document.getElementById('breathing-circle');
|
||||
const text = document.getElementById('breathing-text');
|
||||
const breathingTechniques = {
|
||||
balance: {
|
||||
name: 'Balance',
|
||||
label: 'Coherent Breathing',
|
||||
phases: [
|
||||
{ name: 'inhale', duration: 5500, label: 'Breathe In', scale: 1.8 },
|
||||
{ name: 'exhale', duration: 5500, label: 'Breathe Out', scale: 1.0 }
|
||||
]
|
||||
},
|
||||
relax: {
|
||||
name: 'Relax',
|
||||
label: '4-7-8 Relief',
|
||||
phases: [
|
||||
{ name: 'inhale', duration: 4000, label: 'Breathe In', scale: 1.8 },
|
||||
{ name: 'hold', duration: 7000, label: 'Hold', scale: 1.8 },
|
||||
{ name: 'exhale', duration: 8000, label: 'Breathe Out', scale: 1.0 }
|
||||
]
|
||||
},
|
||||
focus: {
|
||||
name: 'Focus',
|
||||
label: 'Box Breathing',
|
||||
phases: [
|
||||
{ name: 'inhale', duration: 4000, label: 'Breathe In', scale: 1.8 },
|
||||
{ name: 'hold', duration: 4000, label: 'Hold', scale: 1.8 },
|
||||
{ name: 'exhale', duration: 4000, label: 'Breathe Out', scale: 1.0 },
|
||||
{ name: 'hold', duration: 4000, label: 'Hold', scale: 1.0 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function startBreathing() {
|
||||
// Create immersive overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'smart-breathing-overlay';
|
||||
overlay.className = 'breathing-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="technique-selector">
|
||||
<button class="technique-btn" onclick="setBreathingTechnique('balance')">Balance</button>
|
||||
<button class="technique-btn" onclick="setBreathingTechnique('relax')">Relax</button>
|
||||
<button class="technique-btn" onclick="setBreathingTechnique('focus')">Focus</button>
|
||||
</div>
|
||||
|
||||
if (breathingPhase === 'inhale') {
|
||||
circle.classList.remove('exhale');
|
||||
circle.classList.add('inhale');
|
||||
text.textContent = 'Breathe In';
|
||||
soundManager.playBreathIn();
|
||||
breathingPhase = 'hold';
|
||||
} else if (breathingPhase === 'hold') {
|
||||
text.textContent = 'Hold';
|
||||
breathingPhase = 'exhale';
|
||||
} else {
|
||||
circle.classList.remove('inhale');
|
||||
circle.classList.add('exhale');
|
||||
text.textContent = 'Breathe Out';
|
||||
soundManager.playBreathOut();
|
||||
breathingPhase = 'inhale';
|
||||
}
|
||||
}, 4000);
|
||||
<div class="breathing-visual-container">
|
||||
<div class="breath-particles"></div>
|
||||
<div id="breath-circle" class="breath-circle-main"></div>
|
||||
<div class="breath-circle-inner"></div>
|
||||
</div>
|
||||
|
||||
<div id="breath-instruction" class="breath-instruction-text">Get Ready...</div>
|
||||
<div id="breath-sub" class="breath-sub-text">Sit comfortably</div>
|
||||
|
||||
<div class="breath-controls">
|
||||
<button class="control-btn-icon" onclick="closeSmartBreathing()">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Start with default or last used
|
||||
setBreathingTechnique('balance');
|
||||
}
|
||||
|
||||
function stopBreathingExercise() {
|
||||
clearInterval(breathingInterval);
|
||||
const circle = document.getElementById('breathing-circle');
|
||||
const text = document.getElementById('breathing-text');
|
||||
if (circle) circle.classList.remove('inhale', 'exhale');
|
||||
if (text) text.textContent = 'Ready';
|
||||
breathingPhase = 'inhale';
|
||||
function setBreathingTechnique(tech) {
|
||||
breathingState.technique = tech;
|
||||
|
||||
// Log session (assuming ~1 min or track actual time)
|
||||
// Update buttons
|
||||
document.querySelectorAll('.technique-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
if(btn.innerText.toLowerCase().includes(breathingTechniques[tech].name.toLowerCase())) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
stopSmartBreathingLoop();
|
||||
startSmartBreathingLoop();
|
||||
}
|
||||
|
||||
function startSmartBreathingLoop() {
|
||||
breathingState.isActive = true;
|
||||
let currentPhaseIndex = 0;
|
||||
|
||||
const loop = async () => {
|
||||
if (!breathingState.isActive) return;
|
||||
|
||||
const technique = breathingTechniques[breathingState.technique];
|
||||
const phase = technique.phases[currentPhaseIndex];
|
||||
|
||||
updateBreathingUI(phase.name, phase.label, phase.duration, phase.scale);
|
||||
|
||||
// Audio & Haptics
|
||||
if (phase.name === 'inhale') {
|
||||
soundManager.playBreathIn();
|
||||
if (navigator.vibrate) navigator.vibrate(50);
|
||||
} else if (phase.name === 'exhale') {
|
||||
soundManager.playBreathOut();
|
||||
if (navigator.vibrate) navigator.vibrate([30, 30]);
|
||||
} else {
|
||||
// Hold
|
||||
if (navigator.vibrate) navigator.vibrate(20);
|
||||
}
|
||||
|
||||
// Wait for phase duration
|
||||
await new Promise(resolve => {
|
||||
breathingState.timer = setTimeout(resolve, phase.duration);
|
||||
});
|
||||
|
||||
// Move to next phase
|
||||
currentPhaseIndex = (currentPhaseIndex + 1) % technique.phases.length;
|
||||
|
||||
if (breathingState.isActive) loop();
|
||||
};
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
function updateBreathingUI(phaseName, label, duration, scale) {
|
||||
const circle = document.getElementById('breath-circle');
|
||||
const text = document.getElementById('breath-instruction');
|
||||
const sub = document.getElementById('breath-sub');
|
||||
|
||||
if (!circle) return;
|
||||
|
||||
// Update Text
|
||||
text.textContent = label;
|
||||
text.style.animation = 'none';
|
||||
text.offsetHeight; // Trigger reflow
|
||||
text.style.animation = 'fadeIn 0.5s';
|
||||
|
||||
sub.textContent = (duration / 1000) + 's';
|
||||
|
||||
// Update Visuals
|
||||
circle.className = 'breath-circle-main ' + phaseName;
|
||||
circle.style.transition = `transform ${duration}ms linear`;
|
||||
circle.style.transform = `scale(${scale})`;
|
||||
}
|
||||
|
||||
function stopSmartBreathingLoop() {
|
||||
breathingState.isActive = false;
|
||||
if (breathingState.timer) clearTimeout(breathingState.timer);
|
||||
}
|
||||
|
||||
function closeSmartBreathing() {
|
||||
stopSmartBreathingLoop();
|
||||
const overlay = document.getElementById('smart-breathing-overlay');
|
||||
if (overlay) overlay.remove();
|
||||
|
||||
// Log session
|
||||
exerciseAPI.logSession('breathing', 60).then(() => {
|
||||
triggerSuccessPing();
|
||||
showSuccessMessage('Breathing session logged! 🌬️');
|
||||
showSuccessMessage('Breathing session complete! 🌬️');
|
||||
updateProgress();
|
||||
});
|
||||
}
|
||||
|
||||
// Make global
|
||||
window.setBreathingTechnique = setBreathingTechnique;
|
||||
window.closeSmartBreathing = closeSmartBreathing;
|
||||
|
||||
// Legacy wrappers
|
||||
function startBreathingExercise() { startBreathing(); }
|
||||
function stopBreathingExercise() { closeSmartBreathing(); }
|
||||
|
||||
|
||||
// Export additional functions
|
||||
window.addEmotionInput = addEmotionInput;
|
||||
window.addGratitudeInput = addGratitudeInput;
|
||||
|
||||
Reference in New Issue
Block a user