- Add 3-column balanced site distribution using greedy weight algorithm - Build new DOM off-screen in DocumentFragment, swap atomically (no flash) - Skip empty API responses and preserve display on transient errors - Remove news ticker from UI and grid layout - Add blue-to-red gradient on site header bars - Bump font sizes: destinations 1.4em, countdowns 1.5em, line numbers 1.6em - Add breathing pulse animation on daylight bar sun/moon icons - Fix daylight bar indicator snapping to position on first render - Make config button visible in landscape with semi-transparent background - Add weather forecast strip as grid row 4 with compact styling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3.7 KiB
JavaScript
106 lines
3.7 KiB
JavaScript
/**
|
|
* Main application entry point
|
|
* Initializes all components when the DOM is ready
|
|
*/
|
|
|
|
import { Constants } from './utils/constants.js';
|
|
import { logger } from './utils/logger.js';
|
|
import { ConfigManager } from './components/ConfigManager.js';
|
|
import { Clock } from './components/Clock.js';
|
|
import { WeatherManager } from './components/WeatherManager.js';
|
|
import { DeparturesManager } from './components/DeparturesManager.js';
|
|
import { NewsTicker } from './components/NewsTicker.js';
|
|
|
|
/**
|
|
* Function to ensure content wrapper exists for rotated orientations
|
|
*/
|
|
function ensureContentWrapper() {
|
|
if (!document.getElementById('content-wrapper')) {
|
|
logger.info('Creating content wrapper');
|
|
const wrapper = document.createElement('div');
|
|
wrapper.id = 'content-wrapper';
|
|
|
|
// Move all body children to the wrapper except excluded elements
|
|
const excludedElements = ['config-button', 'config-modal', 'background-overlay'];
|
|
|
|
// Create an array of nodes to move (can't modify while iterating)
|
|
const nodesToMove = [];
|
|
for (let i = 0; i < document.body.children.length; i++) {
|
|
const child = document.body.children[i];
|
|
if (!excludedElements.includes(child.id) && child.id !== 'content-wrapper') {
|
|
nodesToMove.push(child);
|
|
}
|
|
}
|
|
|
|
// Move the nodes to the wrapper
|
|
nodesToMove.forEach(node => {
|
|
wrapper.appendChild(node);
|
|
});
|
|
|
|
// Add the wrapper back to the body
|
|
document.body.appendChild(wrapper);
|
|
}
|
|
}
|
|
|
|
// Initialize components when the DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
logger.info('DOM fully loaded');
|
|
|
|
try {
|
|
// Initialize ConfigManager first
|
|
logger.info('Creating ConfigManager...');
|
|
window.configManager = new ConfigManager({
|
|
defaultOrientation: 'normal',
|
|
defaultDarkMode: 'auto'
|
|
});
|
|
|
|
// Initialize Clock
|
|
const timezone = Constants.TIMEZONE || 'Europe/Stockholm';
|
|
window.clock = new Clock({
|
|
elementId: 'clock',
|
|
timezone: timezone
|
|
});
|
|
|
|
// Initialize WeatherManager with location from window config or constants
|
|
const defaultLat = window.DEFAULT_LOCATION?.latitude ||
|
|
Constants.DEFAULT_LOCATION.LATITUDE || 59.3293;
|
|
const defaultLon = window.DEFAULT_LOCATION?.longitude ||
|
|
Constants.DEFAULT_LOCATION.LONGITUDE || 18.0686;
|
|
window.weatherManager = new WeatherManager({
|
|
latitude: defaultLat,
|
|
longitude: defaultLon
|
|
});
|
|
|
|
// Initialize DeparturesManager
|
|
window.departuresManager = new DeparturesManager({
|
|
containerId: 'departures',
|
|
statusId: 'status',
|
|
lastUpdatedId: 'last-updated'
|
|
});
|
|
|
|
// NewsTicker disabled - ticker removed from UI
|
|
|
|
// Set up event listeners
|
|
document.addEventListener('darkModeChanged', event => {
|
|
document.body.classList.toggle('dark-mode', event.detail.isDarkMode);
|
|
});
|
|
|
|
document.addEventListener('configChanged', event => {
|
|
if (['vertical', 'upsidedown', 'vertical-reverse'].includes(event.detail.config.orientation)) {
|
|
ensureContentWrapper();
|
|
}
|
|
});
|
|
|
|
// Ensure content wrapper exists initially
|
|
ensureContentWrapper();
|
|
|
|
logger.info('All components initialized successfully');
|
|
} catch (error) {
|
|
logger.error('Error during initialization:', error);
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'error';
|
|
errorDiv.textContent = `Initialization error: ${error.message}`;
|
|
document.body.appendChild(errorDiv);
|
|
}
|
|
});
|