mirror of
https://github.com/Alvin-Zilverstand/narrow_casting_system.git
synced 2026-03-06 02:57:17 +01:00
424 lines
13 KiB
JavaScript
424 lines
13 KiB
JavaScript
// Display Management for SnowWorld Client
|
|
class DisplayManager {
|
|
constructor() {
|
|
this.currentContent = [];
|
|
this.currentIndex = 0;
|
|
this.contentTimer = null;
|
|
this.transitionDuration = 1000; // 1 second
|
|
this.isPlaying = false;
|
|
this.zone = this.getZoneFromURL() || 'reception';
|
|
this.zoneData = null;
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
this.setupEventListeners();
|
|
await this.loadZoneData();
|
|
this.updateZoneDisplay();
|
|
this.hideLoadingScreen();
|
|
}
|
|
|
|
async loadZoneData() {
|
|
try {
|
|
const response = await fetch(`http://localhost:3000/api/zones`);
|
|
if (!response.ok) throw new Error('Failed to fetch zones');
|
|
const zones = await response.json();
|
|
this.zoneData = zones.find(z => z.id === this.zone) || null;
|
|
} catch (error) {
|
|
console.error('Error loading zone data:', error);
|
|
this.zoneData = null;
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Handle visibility change (tab switching)
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
this.pause();
|
|
} else {
|
|
this.resume();
|
|
}
|
|
});
|
|
|
|
// Handle window focus/blur
|
|
window.addEventListener('blur', () => this.pause());
|
|
window.addEventListener('focus', () => this.resume());
|
|
|
|
// Handle errors
|
|
window.addEventListener('error', (e) => {
|
|
console.error('Display error:', e.error);
|
|
this.handleError(e.error);
|
|
});
|
|
}
|
|
|
|
getZoneFromURL() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
return urlParams.get('zone');
|
|
}
|
|
|
|
updateZoneDisplay() {
|
|
const zoneElement = document.getElementById('currentZone');
|
|
const zoneIconElement = document.querySelector('#zoneIndicator .zone-info i');
|
|
|
|
if (zoneElement) {
|
|
zoneElement.textContent = this.getZoneDisplayName(this.zone);
|
|
}
|
|
|
|
// Update icon if we have zone data
|
|
if (zoneIconElement && this.zoneData && this.zoneData.icon) {
|
|
zoneIconElement.className = `fas ${this.zoneData.icon}`;
|
|
}
|
|
}
|
|
|
|
getZoneDisplayName(zoneId) {
|
|
// Use zone data from server if available
|
|
if (this.zoneData && this.zoneData.name) {
|
|
return this.zoneData.name;
|
|
}
|
|
|
|
// Fallback to hardcoded names
|
|
const zoneNames = {
|
|
'reception': 'Receptie',
|
|
'restaurant': 'Restaurant',
|
|
'skislope': 'Skibaan',
|
|
'lockers': 'Kluisjes',
|
|
'shop': 'Winkel',
|
|
'all': 'Algemeen'
|
|
};
|
|
return zoneNames[zoneId] || zoneId;
|
|
}
|
|
|
|
async loadContent(contentList) {
|
|
try {
|
|
console.log('Loading content for zone:', this.zone);
|
|
|
|
// Filter content for current zone
|
|
this.currentContent = contentList.filter(item =>
|
|
item.zone === this.zone || item.zone === 'all'
|
|
);
|
|
|
|
if (this.currentContent.length === 0) {
|
|
this.showPlaceholder();
|
|
return;
|
|
}
|
|
|
|
// Sort content by priority and creation date
|
|
this.currentContent.sort((a, b) => {
|
|
const priorityA = a.priority || 0;
|
|
const priorityB = b.priority || 0;
|
|
if (priorityA !== priorityB) return priorityB - priorityA;
|
|
return new Date(b.createdAt) - new Date(a.createdAt);
|
|
});
|
|
|
|
console.log(`Loaded ${this.currentContent.length} content items`);
|
|
|
|
// Start playback
|
|
this.startPlayback();
|
|
|
|
} catch (error) {
|
|
console.error('Error loading content:', error);
|
|
this.showError();
|
|
}
|
|
}
|
|
|
|
startPlayback() {
|
|
if (this.currentContent.length === 0) {
|
|
this.showPlaceholder();
|
|
return;
|
|
}
|
|
|
|
this.isPlaying = true;
|
|
this.currentIndex = 0;
|
|
|
|
// Show first content item
|
|
this.showContentItem(this.currentContent[0]);
|
|
|
|
// Set up automatic progression
|
|
this.scheduleNextContent();
|
|
}
|
|
|
|
showContentItem(contentItem) {
|
|
const display = document.getElementById('contentDisplay');
|
|
if (!display) return;
|
|
|
|
// Create content element
|
|
const contentElement = this.createContentElement(contentItem);
|
|
|
|
// Clear previous content with fade out
|
|
this.clearCurrentContent(() => {
|
|
display.appendChild(contentElement);
|
|
|
|
// Fade in new content
|
|
setTimeout(() => {
|
|
contentElement.classList.add('active');
|
|
}, 50);
|
|
});
|
|
}
|
|
|
|
createContentElement(contentItem) {
|
|
const element = document.createElement('div');
|
|
element.className = 'content-item';
|
|
element.dataset.contentId = contentItem.id;
|
|
|
|
switch (contentItem.type) {
|
|
case 'image':
|
|
element.innerHTML = `
|
|
<img src="${contentItem.url}" alt="${contentItem.title}">
|
|
`;
|
|
// Handle image load errors
|
|
element.querySelector('img').onerror = () => {
|
|
this.handleContentError(contentItem, 'image');
|
|
};
|
|
break;
|
|
|
|
case 'video':
|
|
element.innerHTML = `
|
|
<video autoplay muted loop>
|
|
<source src="${contentItem.url}" type="${contentItem.mimeType}">
|
|
Uw browser ondersteunt geen video tags.
|
|
</video>
|
|
`;
|
|
// Handle video errors
|
|
element.querySelector('video').onerror = () => {
|
|
this.handleContentError(contentItem, 'video');
|
|
};
|
|
break;
|
|
|
|
case 'livestream':
|
|
element.innerHTML = `
|
|
<div class="content-placeholder">
|
|
<i class="fas fa-broadcast-tower"></i>
|
|
<h3>Livestream</h3>
|
|
<p>${contentItem.title}</p>
|
|
</div>
|
|
`;
|
|
break;
|
|
|
|
case 'text':
|
|
element.innerHTML = `
|
|
<div class="text-content">
|
|
<h2 class="text-content-title">${contentItem.title}</h2>
|
|
<div class="text-content-body">${contentItem.textContent}</div>
|
|
</div>
|
|
`;
|
|
break;
|
|
|
|
default:
|
|
element.innerHTML = `
|
|
<div class="content-placeholder">
|
|
<i class="fas fa-file-alt"></i>
|
|
<h3>${contentItem.title}</h3>
|
|
<p>Type: ${contentItem.type}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
handleContentError(contentItem, type) {
|
|
console.error(`Error loading ${type}:`, contentItem);
|
|
|
|
// Replace with error placeholder
|
|
const element = document.querySelector(`[data-content-id="${contentItem.id}"]`);
|
|
if (element) {
|
|
element.innerHTML = `
|
|
<div class="content-placeholder error">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<h3>Fout bij laden</h3>
|
|
<p>${type} kon niet worden geladen</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
clearCurrentContent(callback) {
|
|
const currentItems = document.querySelectorAll('.content-item');
|
|
let itemsToRemove = currentItems.length;
|
|
|
|
if (itemsToRemove === 0) {
|
|
if (callback) callback();
|
|
return;
|
|
}
|
|
|
|
currentItems.forEach(item => {
|
|
item.classList.remove('active');
|
|
item.classList.add('content-fade-out');
|
|
|
|
setTimeout(() => {
|
|
item.remove();
|
|
itemsToRemove--;
|
|
|
|
if (itemsToRemove === 0 && callback) {
|
|
callback();
|
|
}
|
|
}, this.transitionDuration);
|
|
});
|
|
}
|
|
|
|
scheduleNextContent() {
|
|
if (!this.isPlaying) return;
|
|
|
|
// Clear existing timer
|
|
if (this.contentTimer) {
|
|
clearTimeout(this.contentTimer);
|
|
}
|
|
|
|
const currentItem = this.currentContent[this.currentIndex];
|
|
const duration = (currentItem.duration || 10) * 1000; // Convert to milliseconds
|
|
|
|
this.contentTimer = setTimeout(() => {
|
|
this.nextContent();
|
|
}, duration);
|
|
}
|
|
|
|
nextContent() {
|
|
if (!this.isPlaying || this.currentContent.length === 0) return;
|
|
|
|
// Move to next content item
|
|
this.currentIndex = (this.currentIndex + 1) % this.currentContent.length;
|
|
|
|
// Show next content
|
|
this.showContentItem(this.currentContent[this.currentIndex]);
|
|
|
|
// Schedule next content
|
|
this.scheduleNextContent();
|
|
}
|
|
|
|
previousContent() {
|
|
if (!this.isPlaying || this.currentContent.length === 0) return;
|
|
|
|
// Move to previous content item
|
|
this.currentIndex = (this.currentIndex - 1 + this.currentContent.length) % this.currentContent.length;
|
|
|
|
// Show previous content
|
|
this.showContentItem(this.currentContent[this.currentIndex]);
|
|
|
|
// Schedule next content
|
|
this.scheduleNextContent();
|
|
}
|
|
|
|
showPlaceholder() {
|
|
const display = document.getElementById('contentDisplay');
|
|
if (!display) return;
|
|
|
|
this.clearCurrentContent(() => {
|
|
const placeholder = document.createElement('div');
|
|
placeholder.className = 'content-item active';
|
|
placeholder.innerHTML = `
|
|
<div class="content-placeholder">
|
|
<i class="fas fa-snowflake"></i>
|
|
<h3>Welkom bij SnowWorld</h3>
|
|
<p>Er is momenteel geen content beschikbaar voor deze zone.</p>
|
|
</div>
|
|
`;
|
|
|
|
display.appendChild(placeholder);
|
|
});
|
|
}
|
|
|
|
showError() {
|
|
const display = document.getElementById('contentDisplay');
|
|
if (!display) return;
|
|
|
|
this.clearCurrentContent(() => {
|
|
const error = document.createElement('div');
|
|
error.className = 'content-item active';
|
|
error.innerHTML = `
|
|
<div class="content-placeholder error">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<h3>Fout bij het laden van content</h3>
|
|
<p>Er is een fout opgetreden. Probeer het opnieuw.</p>
|
|
</div>
|
|
`;
|
|
|
|
display.appendChild(error);
|
|
});
|
|
}
|
|
|
|
pause() {
|
|
this.isPlaying = false;
|
|
if (this.contentTimer) {
|
|
clearTimeout(this.contentTimer);
|
|
}
|
|
console.log('Display paused');
|
|
}
|
|
|
|
resume() {
|
|
if (!this.isPlaying && this.currentContent.length > 0) {
|
|
this.isPlaying = true;
|
|
this.scheduleNextContent();
|
|
console.log('Display resumed');
|
|
}
|
|
}
|
|
|
|
stop() {
|
|
this.isPlaying = false;
|
|
if (this.contentTimer) {
|
|
clearTimeout(this.contentTimer);
|
|
}
|
|
this.clearCurrentContent();
|
|
console.log('Display stopped');
|
|
}
|
|
|
|
updateContent(newContent) {
|
|
console.log('Updating content...');
|
|
|
|
// Stop current playback
|
|
this.stop();
|
|
|
|
// Load new content
|
|
this.loadContent(newContent);
|
|
}
|
|
|
|
async setZone(zone) {
|
|
if (this.zone !== zone) {
|
|
console.log(`Zone changed from ${this.zone} to ${zone}`);
|
|
this.zone = zone;
|
|
await this.loadZoneData();
|
|
this.updateZoneDisplay();
|
|
|
|
// Request new content for this zone
|
|
if (window.connectionManager) {
|
|
window.connectionManager.requestContentForZone(zone);
|
|
}
|
|
}
|
|
}
|
|
|
|
hideLoadingScreen() {
|
|
const loadingScreen = document.getElementById('loadingScreen');
|
|
if (loadingScreen) {
|
|
loadingScreen.classList.add('hidden');
|
|
setTimeout(() => {
|
|
loadingScreen.style.display = 'none';
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
handleError(error) {
|
|
console.error('Display error:', error);
|
|
this.showError();
|
|
|
|
// Show error overlay
|
|
const errorOverlay = document.getElementById('errorOverlay');
|
|
if (errorOverlay) {
|
|
document.getElementById('errorMessage').textContent =
|
|
'Kan geen content laden. Controleer de verbinding.';
|
|
errorOverlay.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// Get current status
|
|
getStatus() {
|
|
return {
|
|
isPlaying: this.isPlaying,
|
|
currentZone: this.zone,
|
|
contentCount: this.currentContent.length,
|
|
currentIndex: this.currentIndex,
|
|
currentContent: this.currentContent[this.currentIndex] || null
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create global display manager instance
|
|
window.displayManager = new DisplayManager(); |