Overview
The app.js module is the entry point and orchestrator of Estudo Organizado. It handles application initialization, view navigation, modal management, and UI interactions like toasts and confirmations.
Key Responsibilities
App Initialization Database setup, sync operations, theme application, and event listeners
Navigation System View routing, sidebar management, and URL state
Modal Management Opening/closing modals, custom confirm dialogs, and prompts
UI Feedback Toast notifications, theme toggling, and user interactions
Application Initialization
The init() Function
The initialization sequence runs when the app loads. It orchestrates database setup, syncing, and UI preparation.
Location: app.js:140
export function init () {
initDB (). then ( async () => {
applyTheme ();
initNotifications ();
// Primary Sync: Cloudflare (Fast)
if ( state . config && state . config . cfSyncSyncEnabled ) {
try {
await pullFromCloudflare ();
} catch ( e ) {
console . error ( 'Falha no Boot Sync (Cloudflare)' , e );
}
}
// Secondary Sync: Google Drive (Slower)
updateDriveUI ();
const savedClientId = localStorage . getItem ( 'estudo_drive_client_id' );
if ( savedClientId ) {
initGoogleAPIs ();
}
navigate ( 'home' );
// Auto Update states
state . eventos . forEach ( ev => {
if ( ev . status === 'agendado' && ev . data && ev . data < todayStr ()) {
ev . status = 'atrasado' ;
}
});
scheduleSave ();
// Check Drive Sync Every 5 Min
setInterval (() => {
if ( typeof gapi !== 'undefined' && gapi . client ?. getToken () !== null && state . driveFileId )
syncWithDrive ();
}, 300000 );
});
}
Initialization Flow Breakdown
Step 1: Database Initialization
Opens IndexedDB connection
Loads persisted state from app_state object store
Falls back to localStorage for legacy migrations
Step 2: UI Setup
Applies saved theme (dark/light mode)
Initializes browser notification permissions
Step 3: Cloud Sync
Attempts Cloudflare sync first (faster, primary)
Then initializes Google Drive sync if configured
Step 4: Navigation & Cleanup
Navigates to home view
Updates overdue events (agendado → atrasado)
Sets up periodic Drive sync every 5 minutes
Navigation System
View Navigation
The navigate() function handles all view transitions throughout the app.
Location: app.js:18
export function navigate ( view ) {
if ( window . innerWidth <= 768 ) closeSidebar ();
if ( view === 'editais' ) {
window . activeDashboardDiscCtx = null ;
}
currentView = view ;
document . querySelectorAll ( '.nav-item' ). forEach ( el => {
el . classList . toggle ( 'active' , el . dataset . view === view );
});
renderCurrentView ();
}
Available Views
Navigation Behavior
View Purpose homeDashboard with stats and metrics medToday’s study events (MED = Meu Estudo Diário) calendarCalendar view of all events cronometroActive timer interface revisoesSpaced repetition review schedule habitosHabit tracking (questions, simulations, etc) editaisExam syllabus management cicloStudy cycle planning configSettings and configuration
Auto-closes sidebar on mobile (≤768px)
Resets context for certain views (e.g., editais)
Updates active navigation item styling
Triggers view re-render via renderCurrentView()
Modal Management
Opening and Closing Modals
Location: app.js:35
export function openModal ( id ) {
document . getElementById ( id ). classList . add ( 'open' );
document . body . style . overflow = 'hidden' ; // Prevent background scroll
}
export function closeModal ( id ) {
document . getElementById ( id ). classList . remove ( 'open' );
document . body . style . overflow = '' ;
}
Custom Confirm Dialog
A reusable confirmation system that replaces native confirm() for better UX.
Location: app.js:46
export let _confirmCallback = null ;
export function showConfirm ( msg , onYes , opts = {}) {
const { title = 'Confirmar' , label = 'Confirmar' , danger = false } = opts ;
document . getElementById ( 'confirm-title' ). textContent = title ;
document . getElementById ( 'confirm-msg' ). textContent = msg ;
const okBtn = document . getElementById ( 'confirm-ok-btn' );
okBtn . textContent = label ;
okBtn . className = `btn btn-sm ${ danger ? 'btn-danger' : 'btn-primary' } ` ;
_confirmCallback = onYes ;
openModal ( 'modal-confirm' );
}
Example: Delete Confirmation
Example: Soft Confirmation
showConfirm (
'Tem certeza que deseja excluir este evento?' ,
() => {
state . eventos = state . eventos . filter ( e => e . id !== eventId );
scheduleSave ();
},
{ danger: true , label: 'Excluir' , title: 'Excluir Evento' }
);
Toast Notifications
Non-blocking, auto-dismissing notifications for user feedback.
Location: app.js:77
export function showToast ( msg , type = '' ) {
const container = document . getElementById ( 'toast-container' );
const last = container . lastElementChild ;
// Prevent duplicate messages
if ( last && last . dataset . msg === msg ) {
last . classList . remove ( 'show' );
void last . offsetWidth ; // Force reflow
last . classList . add ( 'show' );
return ;
}
// Limit to 3 toasts max
while ( container . children . length >= 3 ) {
const oldest = container . firstElementChild ;
oldest . classList . remove ( 'show' );
setTimeout (() => oldest . remove (), 300 );
}
const toast = document . createElement ( 'div' );
toast . className = `toast ${ type } ` ;
toast . setAttribute ( 'role' , 'status' );
toast . setAttribute ( 'aria-live' , 'polite' );
toast . dataset . msg = msg ;
const icons = { success: '✅' , error: '❌' , info: 'ℹ️' };
const iconSpan = document . createElement ( 'span' );
iconSpan . textContent = icons [ type ] || '💬' ;
const msgSpan = document . createElement ( 'span' );
msgSpan . textContent = msg ;
toast . appendChild ( iconSpan );
toast . appendChild ( document . createTextNode ( ' ' ));
toast . appendChild ( msgSpan );
container . appendChild ( toast );
requestAnimationFrame (() => { toast . classList . add ( 'show' ); });
setTimeout (() => {
toast . classList . remove ( 'show' );
setTimeout (() => toast . remove (), 300 );
}, 3500 );
}
Success Toast showToast ( 'Evento criado!' , 'success' );
Error Toast showToast ( 'Erro ao salvar' , 'error' );
Info Toast showToast ( 'Sincronizando...' , 'info' );
Theme Management
Location: app.js:127
export function applyTheme ( toggle = false ) {
if ( toggle ) {
state . config . darkMode = ! state . config . darkMode ;
state . config . tema = state . config . darkMode ? 'dark' : 'light' ;
scheduleSave ();
}
const theme = state . config . tema || ( state . config . darkMode ? 'dark' : 'light' );
document . documentElement . setAttribute ( 'data-theme' , theme );
const btn = document . getElementById ( 'theme-toggle-btn' );
if ( btn ) btn . textContent = state . config . darkMode ? '☀️ Modo claro' : '🌙 Modo escuro' ;
}
The theme is applied to the root <html> element via the data-theme attribute, which CSS variables respond to for dynamic styling.
Cycle Wizard (Study Cycle Planning)
The cycle wizard is a multi-step modal that helps users configure study cycles.
Location: app.js:252-356
Step 1: Define Disciplines
Users add disciplines with names, colors, and time allocations (in minutes per block). _wizardDisc . push ({
id: 'cdisc_' + Date . now () + i ,
nome: val ,
cor: colors [ i ]. value || '#3b82f6' ,
planejadoMin: minVal ,
estudadoMin: 0 ,
concluido: false
});
Step 2: Arrange Order
Drag-and-drop interface to reorder disciplines for the study cycle sequence. Uses HTML5 Drag & Drop API with wizardDragStart, wizardDragOver, wizardDrop handlers.
Save Cycle
Saves the cycle configuration to state.ciclo and triggers a full re-render. state . ciclo . ativo = true ;
state . ciclo . disciplinas = reordered ;
scheduleSave ();
Interactive Prompts
Exam Date Prompt
Location: app.js:183
export function promptDataProva () {
const atual = state . config . dataProva || '' ;
document . getElementById ( 'modal-prompt-title' ). textContent = 'Data da Prova' ;
document . getElementById ( 'modal-prompt-body' ). innerHTML = `
<div style="margin-bottom:12px;color:var(--text-secondary);font-size:14px;">
Informe a data final para os contadores regressivos.
</div>
<input type="date" id="prompt-input-data" class="form-control" value=" ${ atual } ">
` ;
const saveBtn = document . getElementById ( 'modal-prompt-save' );
saveBtn . onclick = () => {
const nova = document . getElementById ( 'prompt-input-data' ). value ;
if ( nova . trim () === '' ) {
state . config . dataProva = null ;
} else {
if ( / ^ \d {4} - \d {2} - \d {2} $ / . test ( nova )) {
state . config . dataProva = nova ;
} else {
showToast ( 'Data inválida.' , 'error' );
return ;
}
}
scheduleSave ();
if ( currentView === 'home' ) renderCurrentView ();
closeModal ( 'modal-prompt' );
};
openModal ( 'modal-prompt' );
}
Weekly Goals Prompt
Location: app.js:213
Allows users to set weekly study goals for hours and questions.
export function promptMetas () {
const horas = state . config . metas ?. horasSemana || 20 ;
const quest = state . config . metas ?. questoesSemana || 150 ;
// Renders input fields for both metrics
// Validates and saves to state.config.metas
}
Summary
Initialization init() orchestrates DB, sync, theme, and navigation setup on app load
Navigation navigate(view) handles view switching and UI state updates
Modals openModal(), closeModal(), showConfirm() manage dialogs
Feedback showToast() provides non-intrusive user notifications