Release v1.0.2: Fix startup syntax error, offline mode, UI improvements

This commit is contained in:
Gemini AI
2025-12-06 23:41:51 +04:00
Unverified
parent 864070b26a
commit 5e9ffe1997
66 changed files with 2596 additions and 216 deletions

View File

@@ -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;