216 lines
7.0 KiB
JavaScript
216 lines
7.0 KiB
JavaScript
// Collect unique webpage URLs from the slide list.
|
|
function getWebpageUrls(sourceSlides) {
|
|
const urls = [];
|
|
(Array.isArray(sourceSlides) ? sourceSlides : []).forEach(function (slide) {
|
|
const content = slide && slide.content ? slide.content : {};
|
|
const regions = slide && slide.template && Array.isArray(slide.template.regions) ? slide.template.regions : [];
|
|
regions.forEach(function (region) {
|
|
if (region.region_type !== 'webpage') {
|
|
return;
|
|
}
|
|
const regionContent = content[region.region_key] || {};
|
|
const url = String(regionContent.value || '').trim();
|
|
if (url && urls.indexOf(url) === -1) {
|
|
urls.push(url);
|
|
}
|
|
});
|
|
});
|
|
return urls;
|
|
}
|
|
|
|
// Filter the slides down to those that are active right now.
|
|
function getActiveSlidesFrom(sourceSlides) {
|
|
const now = new Date();
|
|
return (Array.isArray(sourceSlides) ? sourceSlides : []).filter(function (slide) {
|
|
return isSlideActive(slide, now);
|
|
});
|
|
}
|
|
|
|
// Build a cache key for the active slide set.
|
|
function getActiveSlidesCacheKey() {
|
|
const now = new Date();
|
|
return [
|
|
currentPlaylistSignature || '',
|
|
now.getFullYear(),
|
|
now.getMonth(),
|
|
now.getDate(),
|
|
now.getHours(),
|
|
now.getMinutes(),
|
|
now.getSeconds()
|
|
].join('|');
|
|
}
|
|
|
|
// Return the cached active slide set for the current playlist and second.
|
|
function getCurrentActiveSlides() {
|
|
const cacheKey = getActiveSlidesCacheKey();
|
|
if (cacheKey !== activeSlidesCacheKey) {
|
|
activeSlidesCacheValue = getActiveSlidesFrom(slides);
|
|
activeSlidesCacheKey = cacheKey;
|
|
}
|
|
return activeSlidesCacheValue;
|
|
}
|
|
|
|
// Clear the cached active slide set.
|
|
function clearActiveSlidesCache() {
|
|
activeSlidesCacheKey = '';
|
|
activeSlidesCacheValue = [];
|
|
}
|
|
|
|
// Reset render caches when the viewport changes.
|
|
function syncRenderCacheViewport() {
|
|
var viewportKey = window.innerWidth + 'x' + window.innerHeight;
|
|
if (renderCacheViewportKey === viewportKey) {
|
|
return;
|
|
}
|
|
|
|
renderCacheViewportKey = viewportKey;
|
|
slideMarkupCache = Object.create(null);
|
|
templateLayoutCache = Object.create(null);
|
|
templateRenderPlanCache = Object.create(null);
|
|
}
|
|
|
|
// Build a signature for the currently rendered view.
|
|
function getCurrentRenderKey(activeSlides) {
|
|
const viewportKey = window.innerWidth + 'x' + window.innerHeight;
|
|
const availableSlides = Array.isArray(activeSlides) ? activeSlides : [];
|
|
if (!availableSlides.length) {
|
|
return [currentPlaylistSignature || '', viewportKey, 'empty', slides.length ? 'scheduled' : 'assigned'].join('|');
|
|
}
|
|
let normalizedIndex = Number(index || 0);
|
|
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
|
|
normalizedIndex = 0;
|
|
}
|
|
const slide = availableSlides[normalizedIndex];
|
|
return [currentPlaylistSignature || '', viewportKey, 'slide', slide && slide.id ? slide.id : ''].join('|');
|
|
}
|
|
|
|
// Pick the current slide and the next slide for webpage preloading.
|
|
function getWebpagePreloadSlides(sourceSlides, targetIndex) {
|
|
const availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
|
|
if (!availableSlides.length) {
|
|
return [];
|
|
}
|
|
|
|
let normalizedIndex = Number(targetIndex || 0);
|
|
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
|
|
normalizedIndex = 0;
|
|
}
|
|
|
|
const preloadSlides = [];
|
|
const currentSlide = availableSlides[normalizedIndex];
|
|
const nextSlide = availableSlides[normalizedIndex + 1];
|
|
|
|
if (currentSlide) {
|
|
preloadSlides.push(currentSlide);
|
|
}
|
|
if (nextSlide && nextSlide !== currentSlide) {
|
|
preloadSlides.push(nextSlide);
|
|
}
|
|
|
|
return preloadSlides;
|
|
}
|
|
|
|
// Mount hidden iframe preloads for the chosen webpage URLs.
|
|
function syncWebpagePreloads(sourceSlides, targetIndex) {
|
|
const urls = getWebpageUrls(getWebpagePreloadSlides(sourceSlides, targetIndex));
|
|
const signature = urls.join('\n');
|
|
if (signature === preloadSignature) {
|
|
return;
|
|
}
|
|
if (!urls.length) {
|
|
preloadSignature = '';
|
|
if (preloadContainer) {
|
|
preloadContainer.innerHTML = '';
|
|
}
|
|
return;
|
|
}
|
|
if (!preloadContainer) {
|
|
preloadContainer = document.createElement('div');
|
|
preloadContainer.className = 'webpage-preloads';
|
|
preloadContainer.setAttribute('aria-hidden', 'true');
|
|
document.body.appendChild(preloadContainer);
|
|
}
|
|
preloadContainer.innerHTML = urls.map(function (url) {
|
|
return '<iframe class="webpage-preload-frame" src="' + escapeHtml(url) + '" title="Webpage preload" tabindex="-1" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe>';
|
|
}).join('');
|
|
preloadSignature = signature;
|
|
}
|
|
|
|
// Return a stable client id for this browser session.
|
|
function getCommandClientId() {
|
|
if (commandClientId) {
|
|
return commandClientId;
|
|
}
|
|
try {
|
|
var storedClientId = window.localStorage.getItem(commandClientStorageKey);
|
|
if (storedClientId) {
|
|
commandClientId = storedClientId;
|
|
return commandClientId;
|
|
}
|
|
} catch (_error) {
|
|
// fall through to ephemeral ID generation
|
|
}
|
|
commandClientId = (window.crypto && window.crypto.randomUUID ? window.crypto.randomUUID() : 'client-' + Date.now() + '-' + Math.random().toString(16).slice(2));
|
|
try {
|
|
window.localStorage.setItem(commandClientStorageKey, commandClientId);
|
|
} catch (_error2) {
|
|
// ignore storage errors
|
|
}
|
|
return commandClientId;
|
|
}
|
|
|
|
// Load the most recent playlist snapshot from browser storage.
|
|
function loadPlaylistSnapshot() {
|
|
try {
|
|
var raw = window.localStorage.getItem(playlistSnapshotStorageKey);
|
|
if (!raw) {
|
|
return null;
|
|
}
|
|
var parsed = JSON.parse(raw);
|
|
if (!parsed || !Array.isArray(parsed.slides)) {
|
|
return null;
|
|
}
|
|
return {
|
|
slides: parsed.slides.map(normalizeSlide),
|
|
signature: String(parsed.signature || ''),
|
|
fadeBetweenSlides: Boolean(parsed.fadeBetweenSlides),
|
|
etag: String(parsed.etag || '')
|
|
};
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Save the latest playlist snapshot for offline recovery.
|
|
function savePlaylistSnapshot(data) {
|
|
try {
|
|
window.localStorage.setItem(playlistSnapshotStorageKey, JSON.stringify({
|
|
slides: Array.isArray(data && data.slides) ? data.slides : [],
|
|
signature: String(data && data.signature || ''),
|
|
fadeBetweenSlides: Boolean(data && data.fadeBetweenSlides),
|
|
etag: String(data && data.etag || ''),
|
|
savedAt: new Date().toISOString()
|
|
}));
|
|
} catch (_error) {
|
|
// ignore storage errors
|
|
}
|
|
}
|
|
|
|
// Apply a playlist snapshot to the current in-memory state.
|
|
function applyPlaylistSnapshot(data) {
|
|
if (!data || !Array.isArray(data.slides)) {
|
|
return false;
|
|
}
|
|
slides = data.slides.map(normalizeSlide);
|
|
currentPlaylistSignature = String(data.signature || '');
|
|
currentPlaylistFadeBetweenSlides = Boolean(data.fadeBetweenSlides);
|
|
currentPlaylistEtag = String(data.etag || '');
|
|
pendingPlaylistUpdate = null;
|
|
clearActiveSlidesCache();
|
|
slideMarkupCache = Object.create(null);
|
|
templateLayoutCache = Object.create(null);
|
|
templateRenderPlanCache = Object.create(null);
|
|
renderCacheViewportKey = '';
|
|
index = 0;
|
|
return true;
|
|
} |