Display Module 13-05-2026

This commit is contained in:
Kevin Adametz 2026-05-13 14:34:08 +02:00
parent 6a65354f4c
commit 9262132325
41 changed files with 496 additions and 334 deletions

View file

@ -411,6 +411,56 @@
.status-message { font-weight: 300; opacity: 0.7; }
.status-error { color: #ef4444; font-weight: 500; }
.status-sub { font-size: 1.4vh; opacity: 0.4; margin-top: 1vh; }
.display-overview {
position: fixed; inset: 0; z-index: 10000;
overflow-y: auto; background: radial-gradient(circle at top, #12364d 0, #05070a 42%, #000 100%);
color: #fff; cursor: auto; padding: clamp(24px, 5vw, 72px);
}
.display-overview.hidden { display: none; }
.display-overview__inner { width: min(1120px, 100%); margin: 0 auto; }
.display-overview__eyebrow {
color: #38bdf8; font-size: 13px; font-weight: 700;
letter-spacing: 0.16em; text-transform: uppercase; margin-bottom: 12px;
}
.display-overview h1 {
font-size: clamp(34px, 6vw, 76px); line-height: 0.95;
letter-spacing: -0.05em; margin-bottom: 18px;
}
.display-overview__intro {
max-width: 720px; color: rgba(255,255,255,0.68);
font-size: clamp(16px, 2vw, 22px); line-height: 1.5; margin-bottom: 36px;
}
.display-overview__grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 18px;
}
.display-card {
display: flex; flex-direction: column; gap: 16px;
min-height: 220px; padding: 24px; border-radius: 28px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(255,255,255,0.08); color: #fff; text-decoration: none;
box-shadow: 0 24px 70px rgba(0,0,0,0.24);
transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.display-card:hover {
transform: translateY(-2px);
border-color: rgba(56,189,248,0.55);
background: rgba(255,255,255,0.12);
}
.display-card__badges { display: flex; flex-wrap: wrap; gap: 8px; }
.display-badge {
border-radius: 999px; padding: 6px 10px; font-size: 12px; font-weight: 700;
background: rgba(34,197,94,0.18); color: #86efac; border: 1px solid rgba(134,239,172,0.28);
}
.display-badge--live { background: rgba(56,189,248,0.18); color: #7dd3fc; border-color: rgba(125,211,252,0.28); }
.display-card__title { font-size: 28px; font-weight: 700; letter-spacing: -0.03em; }
.display-card__meta { display: grid; gap: 6px; color: rgba(255,255,255,0.62); font-size: 15px; }
.display-card__action { margin-top: auto; color: #7dd3fc; font-weight: 700; }
.display-overview__empty {
border: 1px dashed rgba(255,255,255,0.24); border-radius: 28px;
padding: 32px; color: rgba(255,255,255,0.62);
}
</style>
</head>
<body>
@ -434,6 +484,18 @@
<div class="status-sub">Neustart in Kürze...</div>
</div>
<div class="display-overview hidden" id="display-overview">
<div class="display-overview__inner">
<div class="display-overview__eyebrow">Cabinet Display Player</div>
<h1>Aktive Live-Displays</h1>
<p class="display-overview__intro">
Wählen Sie ein Display aus, um die veröffentlichte Live-Bespielung zu öffnen.
Angezeigt werden nur aktive Displays mit veröffentlichter Live-Konfiguration.
</p>
<div class="display-overview__grid" id="display-overview-list"></div>
</div>
</div>
<script>
function escapeHtml(value) {
const div = document.createElement('div');
@ -462,15 +524,12 @@ class DisplayPlayer {
this.moduleId = this.detectModuleId();
this.itemId = this.detectItemId();
this.displayId = this.detectDisplayId();
if (!this.displayId && !this.previewToken && !this.moduleId) {
this.showError('Keine Display-ID oder Vorschau angegeben. URL: /display/index.html?id=1');
return;
}
// API
this.BASE_URL = this.detectBaseUrl();
this.API_CONFIG = this.detectConfigUrl();
this.API_CHECK = this.detectCheckUrl();
this.API_OVERVIEW = `${this.BASE_URL}/api/display/overview`;
// Timing
this.POLL_INTERVAL = 60000;
@ -497,6 +556,8 @@ class DisplayPlayer {
this.loadingInfo = document.getElementById('loading-info');
this.errorOverlay = document.getElementById('error-overlay');
this.errorMessage = document.getElementById('error-message');
this.overviewOverlay = document.getElementById('display-overview');
this.overviewList = document.getElementById('display-overview-list');
this.loadingInfo.textContent = this.detectLoadingLabel();
@ -583,13 +644,16 @@ class DisplayPlayer {
}
return `Modul #${this.moduleId}`;
}
if (!this.displayId) {
return 'Display-Übersicht';
}
return `Display #${this.displayId}`;
}
detectBaseUrl() {
const hostname = window.location.hostname;
if (hostname === 'cabinet.b2in.eu' || hostname.includes('b2in.eu')) {
return 'https://b2in.eu';
if (hostname === 'cabinet.b2in.eu') {
return 'https://portal.b2in.eu';
}
return window.location.origin;
}
@ -602,6 +666,11 @@ class DisplayPlayer {
console.log(`[Display] Initializing ${this.detectLoadingLabel()}`);
try {
if (!this.displayId && !this.previewToken && !this.moduleId) {
await this.fetchOverview();
return;
}
await this.fetchConfig();
if (this.playlist.length === 0) {
@ -642,6 +711,16 @@ class DisplayPlayer {
console.log(`[Display] Loaded ${this.playlist.length} version(s)`);
}
async fetchOverview() {
const response = await fetch(this.API_OVERVIEW);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
this.renderOverview(data.displays || []);
}
startPolling() {
if (!this.API_CHECK) {
return;
@ -779,11 +858,50 @@ class DisplayPlayer {
this.loadingOverlay.classList.add('hidden');
}
renderOverview(displays) {
this.hideLoading();
this.errorOverlay.classList.add('hidden');
this.overviewOverlay.classList.remove('hidden');
if (displays.length === 0) {
this.overviewList.innerHTML = `
<div class="display-overview__empty">
Es sind aktuell keine aktiven Live-Displays veröffentlicht.
</div>
`;
return;
}
this.overviewList.innerHTML = displays.map(display => `
<a class="display-card" href="${this.escapeHtml(display.url)}">
<div class="display-card__badges">
<span class="display-badge">Aktiv</span>
<span class="display-badge display-badge--live">Live</span>
</div>
<div>
<div class="display-card__title">${this.escapeHtml(display.name)}</div>
<div class="display-card__meta">
<span>Display-ID: ${this.escapeHtml(display.id)}</span>
${display.location ? `<span>Standort: ${this.escapeHtml(display.location)}</span>` : ''}
<span>${this.escapeHtml(display.module_count)} Modul(e) veröffentlicht</span>
</div>
</div>
<div class="display-card__action">Display öffnen</div>
</a>
`).join('');
}
showError(msg) {
this.loadingOverlay.classList.add('hidden');
this.errorOverlay.classList.remove('hidden');
this.errorMessage.textContent = msg;
}
escapeHtml(value) {
const div = document.createElement('div');
div.textContent = value ?? '';
return div.innerHTML;
}
}