Save worktree changes

This commit is contained in:
2026-07-25 02:29:19 +01:00
parent 8d3b7d557b
commit db9d718cd8
170 changed files with 11719 additions and 3414 deletions
@@ -0,0 +1,354 @@
// Capture the current viewport dimensions.
function getCurrentViewport() {
return {
width: window.innerWidth,
height: window.innerHeight
};
}
// Command websocket and player-state helpers.
// Send the current playback state to the command websocket.
function sendCommandState(currentSlide) {
if (!commandSocket || commandSocket.readyState !== WebSocket.OPEN) {
return;
}
commandSocket.send(JSON.stringify({
type: 'state',
clientId: getCommandClientId(),
clientName: getOnboardingClientName() || null,
deviceId: getOnboardingDeviceId() || null,
userAgent: window.navigator.userAgent || '',
page: window.location.href,
viewport: getCurrentViewport(),
paused: isPaused,
blackout: isBlackout,
currentSlide: currentSlide ? {
id: currentSlide.id || null,
title: currentSlide.title || '',
kind: currentSlide.kind || '',
playlistSignature: currentPlaylistSignature || ''
} : null
}));
}
// Debounce command-state updates during rapid changes.
function scheduleCommandStateUpdate() {
if (commandStateTimer) {
window.clearTimeout(commandStateTimer);
}
commandStateTimer = window.setTimeout(function () {
commandStateTimer = null;
sendCommandState(lastRenderedSlide);
}, 300);
}
// Debounce rerenders after viewport changes.
function scheduleViewportRenderUpdate() {
if (viewportRenderTimer) {
window.clearTimeout(viewportRenderTimer);
}
viewportRenderTimer = window.setTimeout(function () {
viewportRenderTimer = null;
if (slides.length) {
var activeSlides = getCurrentActiveSlides();
if (getCurrentRenderKey(activeSlides) === lastRenderedViewKey) {
return;
}
showCurrent();
}
}, 150);
}
// Cancel the current slide-advance timer.
function clearSlideTimer() {
if (timer) {
window.clearTimeout(timer);
timer = null;
}
}
// Schedule the next slide transition.
function scheduleSlideAdvance(delayMs) {
clearSlideTimer();
var holdDelayMs = Math.max(1, Number(delayMs || 0));
slideExpiresAt = Date.now() + holdDelayMs;
timer = window.setTimeout(function () {
timer = null;
slideExpiresAt = null;
pausedRemainingMs = null;
const activeSlides = getCurrentActiveSlides();
if (activeSlides.length < 2) {
refresh();
return;
}
if (index >= activeSlides.length) {
index = 0;
}
index = (index + 1) % activeSlides.length;
showCurrent();
}, holdDelayMs);
}
// Cancel any pending fade-transition cleanup.
function clearSlideTransitionTimer() {
if (slideTransitionTimer) {
window.clearTimeout(slideTransitionTimer);
slideTransitionTimer = null;
}
}
// Swap slide markup with optional fade animation.
function renderSlideMarkup(markup, shouldFade) {
clearSlideTransitionTimer();
if (typeof destroyRtmpRegions === 'function') {
destroyRtmpRegions(app);
}
if (!shouldFade) {
app.innerHTML = markup;
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(app);
}
return app.firstElementChild;
}
var topLevelChildren = Array.prototype.slice.call(app.children || []);
var existingShells = topLevelChildren.filter(function (child) {
return child && child.classList && child.classList.contains('slide-shell');
});
var previousShell = existingShells.length ? existingShells[existingShells.length - 1] : app.firstElementChild;
if (existingShells.length > 1) {
existingShells.slice(0, -1).forEach(function (shell) {
if (shell && shell.parentNode) {
shell.parentNode.removeChild(shell);
}
});
}
var nextShell = document.createElement('div');
nextShell.className = 'slide-shell';
nextShell.style.opacity = '0';
nextShell.innerHTML = markup;
if (!previousShell || (previousShell.classList && previousShell.classList.contains('empty'))) {
app.innerHTML = '';
nextShell.style.opacity = '1';
app.appendChild(nextShell);
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(nextShell);
}
return nextShell;
}
if (!previousShell.classList.contains('slide-shell')) {
previousShell.classList.add('slide-shell');
}
previousShell.style.opacity = '1';
app.appendChild(nextShell);
void nextShell.offsetHeight;
window.requestAnimationFrame(function () {
nextShell.style.opacity = '1';
previousShell.style.opacity = '0';
});
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(nextShell);
}
slideTransitionTimer = window.setTimeout(function () {
if (previousShell && previousShell.parentNode) {
previousShell.parentNode.removeChild(previousShell);
}
if (nextShell) {
nextShell.style.opacity = '1';
}
slideTransitionTimer = null;
}, slideFadeDurationMs);
return nextShell;
}
// Mirror blackout state onto the document body.
function syncBlackoutState() {
document.body.classList.toggle('screen-blackout', isBlackout);
}
// Apply pause state and preserve remaining slide time.
function setPaused(nextPaused) {
var normalized = Boolean(nextPaused);
if (isPaused === normalized) {
return;
}
if (normalized) {
pausedRemainingMs = slideExpiresAt ? Math.max(0, slideExpiresAt - Date.now()) : null;
isPaused = true;
clearSlideTimer();
sendCommandState(lastRenderedSlide);
return;
}
isPaused = false;
sendCommandState(lastRenderedSlide);
if (!slides.length || !lastRenderedSlide) {
return;
}
if (pausedRemainingMs !== null) {
scheduleSlideAdvance(pausedRemainingMs);
pausedRemainingMs = null;
}
}
// Apply blackout state and notify the server.
function setBlackout(nextBlackout) {
var normalized = Boolean(nextBlackout);
if (isBlackout === normalized) {
return;
}
isBlackout = normalized;
syncBlackoutState();
sendCommandState(lastRenderedSlide);
}
// Coerce command payload values into booleans or null.
function normalizeBoolean(value) {
if (value === true || value === false) {
return value;
}
if (value === null || value === undefined) {
return null;
}
var normalized = String(value).trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].indexOf(normalized) !== -1) {
return true;
}
if (['0', 'false', 'no', 'off', ''].indexOf(normalized) !== -1) {
return false;
}
return null;
}
// Move to the previous or next active slide.
function navigateSlides(offset) {
const manualSlides = getCurrentActiveSlides();
if (!manualSlides.length) {
return;
}
let currentIndex = manualSlides.findIndex(function (slide) {
return slide && lastRenderedSlide && slide.id === lastRenderedSlide.id;
});
if (currentIndex < 0) {
currentIndex = Math.min(Math.max(index, 0), manualSlides.length - 1);
}
const nextIndex = (currentIndex + offset + manualSlides.length) % manualSlides.length;
clearSlideTimer();
applyPendingPlaylistUpdate();
renderSlideAtIndex(manualSlides, nextIndex);
}
// Route incoming websocket command messages.
function handleCommandMessage(rawMessage) {
var payload;
try {
payload = JSON.parse(String(rawMessage || ''));
} catch (_error) {
return;
}
if (!payload || payload.type !== 'command') {
return;
}
switch (payload.command) {
case 'refresh':
refresh();
return;
case 'setclientname':
if (payload.clientName) {
applyOnboardingClientName(payload.clientName, commandSocket);
}
return;
case 'redirect':
if (payload.url) {
window.location.replace(String(payload.url));
}
return;
case 'pause':
setPaused(!isPaused);
return;
case 'blackout':
var desiredBlackout = normalizeBoolean(payload.blackout);
if (desiredBlackout !== null) {
setBlackout(desiredBlackout);
} else {
setBlackout(!isBlackout);
}
return;
case 'previous':
case 'left':
navigateSlides(-1);
return;
case 'next':
case 'right':
navigateSlides(1);
return;
case 'reload':
window.location.reload();
return;
}
}
// Retry the command websocket after a disconnect.
function scheduleCommandReconnect() {
if (commandReconnectTimer) {
return;
}
commandReconnectTimer = window.setTimeout(function () {
commandReconnectTimer = null;
connectCommandSocket();
}, 5000);
}
// Open and wire the command websocket connection.
function connectCommandSocket() {
if (!window.WebSocket) {
return;
}
if (commandSocket && (commandSocket.readyState === WebSocket.OPEN || commandSocket.readyState === WebSocket.CONNECTING)) {
return;
}
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var socketUrl = new URL(commandSocketPath, window.location.origin);
if (window.__pulsePageAuthToken) {
socketUrl.searchParams.set('auth', window.__pulsePageAuthToken);
}
var socket = new WebSocket(socketUrl.toString());
commandSocket = socket;
socket.onopen = function () {
if (typeof syncOnboardingClientNameFromServer === 'function') {
syncOnboardingClientNameFromServer(socket).then(function () {
sendCommandHello(socket);
});
return;
}
sendCommandHello(socket);
};
socket.onmessage = function (event) {
handleCommandMessage(event.data);
};
socket.onclose = function () {
commandSocket = null;
scheduleCommandReconnect();
};
socket.onerror = function () {
try {
socket.close();
} catch (_error) {
// ignore socket close errors
}
};
}