Release 1.6.2
This commit is contained in:
@@ -210,8 +210,10 @@ Per-display option:
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Headlines are fetched server-side via `GET /api/display/<token>/ticker` and cached briefly.
|
||||
- The player reads the company ticker settings via `GET /api/display/<token>/playlist` and refreshes headlines periodically.
|
||||
- Headlines are fetched server-side via `GET /api/display/<token>/ticker` and cached in-memory.
|
||||
- The player reads the company ticker settings via `GET /api/display/<token>/playlist`.
|
||||
- The player auto-refreshes headlines without restart on a **long interval** (default: **12 hours**, override via `?ticker_poll=seconds`).
|
||||
- Server-side cache TTL defaults to **6 hours** (override via env var `TICKER_CACHE_TTL_SECONDS`).
|
||||
|
||||
## SMTP / Forgot password
|
||||
|
||||
@@ -290,5 +292,6 @@ If the reset email is not received:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
@@ -20,7 +21,18 @@ MAX_ACTIVE_SESSIONS_PER_DISPLAY = 3
|
||||
SESSION_TTL_SECONDS = 90
|
||||
|
||||
# RSS ticker cache (in-memory; OK for this small app; avoids hammering feeds)
|
||||
TICKER_CACHE_TTL_SECONDS = 120
|
||||
#
|
||||
# Default is intentionally long because displays can refresh headlines on a long interval
|
||||
# (e.g., twice per day) and we don't want many displays to re-fetch the same feed.
|
||||
# Override via env var `TICKER_CACHE_TTL_SECONDS`.
|
||||
def _env_int(name: str, default: int) -> int:
|
||||
try:
|
||||
return int(os.environ.get(name, "") or default)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
TICKER_CACHE_TTL_SECONDS = max(10, _env_int("TICKER_CACHE_TTL_SECONDS", 6 * 60 * 60))
|
||||
_TICKER_CACHE: dict[str, dict] = {}
|
||||
|
||||
|
||||
|
||||
@@ -280,6 +280,13 @@ h1, h2, h3, .display-1, .display-2, .display-3 {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/* Mobile: dashboard display previews are heavy (iframes). Hide them on small screens. */
|
||||
@media (max-width: 768px) {
|
||||
.display-gallery-card .display-preview {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.display-gallery-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
<iframe
|
||||
title="Preview — {{ d.name }}"
|
||||
data-display-id="{{ d.id }}"
|
||||
src="{{ url_for('display.display_player', token=d.token) }}?preview=1"
|
||||
class="js-display-preview"
|
||||
data-preview-src="{{ url_for('display.display_player', token=d.token) }}?preview=1"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
style="width: 100%; height: 100%; border: 0;"
|
||||
@@ -241,17 +242,23 @@
|
||||
|
||||
function refreshPreviewIframe(displayId) {
|
||||
const iframe = document.querySelector(`iframe[data-display-id="${displayId}"]`);
|
||||
if (!iframe || !iframe.src) return;
|
||||
// Previews are disabled on mobile.
|
||||
if (window.matchMedia && window.matchMedia('(max-width: 768px)').matches) return;
|
||||
if (!iframe) return;
|
||||
try {
|
||||
const u = new URL(iframe.src, window.location.origin);
|
||||
const baseSrc = iframe.dataset.previewSrc || iframe.src;
|
||||
if (!baseSrc) return;
|
||||
const u = new URL(baseSrc, window.location.origin);
|
||||
// Ensure preview flag is present (and bust cache).
|
||||
u.searchParams.set('preview', '1');
|
||||
u.searchParams.set('_ts', String(Date.now()));
|
||||
iframe.src = u.toString();
|
||||
} catch (e) {
|
||||
// Fallback: naive cache buster
|
||||
const sep = iframe.src.includes('?') ? '&' : '?';
|
||||
iframe.src = `${iframe.src}${sep}_ts=${Date.now()}`;
|
||||
const baseSrc = iframe.dataset.previewSrc || iframe.src;
|
||||
if (!baseSrc) return;
|
||||
const sep = baseSrc.includes('?') ? '&' : '?';
|
||||
iframe.src = `${baseSrc}${sep}_ts=${Date.now()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +474,31 @@
|
||||
if (plSaveBtn) {
|
||||
plSaveBtn.addEventListener('click', savePlaylists);
|
||||
}
|
||||
|
||||
// Disable dashboard previews on small screens (mobile): don't even set iframe src.
|
||||
function loadDashboardPreviewsIfDesktop() {
|
||||
const isMobile = window.matchMedia && window.matchMedia('(max-width: 768px)').matches;
|
||||
if (isMobile) return;
|
||||
document.querySelectorAll('iframe.js-display-preview[data-preview-src]').forEach((iframe) => {
|
||||
if (!iframe.src || iframe.src === 'about:blank') {
|
||||
iframe.src = iframe.dataset.previewSrc;
|
||||
}
|
||||
});
|
||||
}
|
||||
loadDashboardPreviewsIfDesktop();
|
||||
|
||||
// If user rotates/resizes from mobile -> desktop, load previews then.
|
||||
if (window.matchMedia) {
|
||||
const mql = window.matchMedia('(max-width: 768px)');
|
||||
const onChange = () => {
|
||||
if (!mql.matches) loadDashboardPreviewsIfDesktop();
|
||||
};
|
||||
if (typeof mql.addEventListener === 'function') {
|
||||
mql.addEventListener('change', onChange);
|
||||
} else if (typeof mql.addListener === 'function') {
|
||||
mql.addListener(onChange);
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -203,6 +203,14 @@
|
||||
let tickerInterval = null;
|
||||
let tickerLastHeadlines = [];
|
||||
|
||||
function getTickerPollSeconds() {
|
||||
// Refresh headlines on a long interval.
|
||||
// Default: 12 hours (twice per day).
|
||||
// Override via ?ticker_poll=seconds.
|
||||
const tp = parseInt(new URLSearchParams(window.location.search).get('ticker_poll') || '', 10);
|
||||
return Number.isFinite(tp) && tp > 0 ? tp : (12 * 60 * 60);
|
||||
}
|
||||
|
||||
const ANIM_MS = 420;
|
||||
|
||||
function getTransitionMode(pl) {
|
||||
@@ -391,8 +399,7 @@
|
||||
clearInterval(tickerInterval);
|
||||
tickerInterval = null;
|
||||
}
|
||||
// Refresh every 2 minutes; server caches too.
|
||||
tickerInterval = setInterval(refreshTickerOnce, 120 * 1000);
|
||||
tickerInterval = setInterval(refreshTickerOnce, getTickerPollSeconds() * 1000);
|
||||
}
|
||||
|
||||
function clearStage() {
|
||||
|
||||
Reference in New Issue
Block a user