From b7f3dd4645aa30a93c0c2469a9207fa0d2f3946b Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 15 Jun 2026 08:52:20 +1000 Subject: [PATCH] v1.5.0 - client: server-controlled defaults, persistent ID, hello/welcome fix, sleep handling --- public/js/app.js | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index e66c474..57c5e49 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,4 +1,4 @@ -// === Frambe v1.4.1 - Client with WebSocket Remote Control === +// === Frambe v1.5.0 - Client with WebSocket Remote Control + Server-Controlled Defaults === (function () { 'use strict'; var config = {}, assets = [], currentIndex = -1, slideshowTimer = null; @@ -6,15 +6,39 @@ var selectedPersonId = null, isRunning = false, refreshTimer = null, urlDriven = false; var currentVideoPlaying = false, pileCanvas, pileCtx; var FRAME_PAD_RATIO = 0.03, FRAME_BOTTOM_RATIO = 0.10, FRAME_COLOR = '#ede8df'; - var wsConn = null, clientId = null, isSleeping = false; - var persistentId = (function(){ var k='frambe_pid'; var v=localStorage.getItem(k); if(vi docker-compose.yml ){ v='fp-'+Math.random().toString(36).substr(2,9)+'-'+Date.now().toString(36); localStorage.setItem(k,v); } return v; })(); - var persistentId = (function(){ var k='frambe_pid'; var v=localStorage.getItem(k); if(vi docker-compose.yml ){ v='fp-'+Math.random().toString(36).substr(2,9)+'-'+Date.now().toString(36); localStorage.setItem(k,v); } return v; })(); + var wsConn = null, clientId = null, isSleeping = false, serverControlled = true; + var persistentId = (function(){ var k='frambe_pid'; var v=localStorage.getItem(k); if(!v){ v='fp-'+Math.random().toString(36).substr(2,9)+'-'+Date.now().toString(36); localStorage.setItem(k,v); } return v; })(); var $setupScreen=document.getElementById('setup-screen'),$slideshowScreen=document.getElementById('slideshow-screen'),$connectionStatus=document.getElementById('connection-status'),$setupContent=document.getElementById('setup-content'),$setupError=document.getElementById('setup-error'),$errorDetail=document.getElementById('error-detail'),$albumsList=document.getElementById('albums-list'),$btnStart=document.getElementById('btn-start'),$bgBlur=document.getElementById('bg-blur'),$mainFrame=document.getElementById('main-frame'),$mainPhoto=document.getElementById('main-photo'),$mainVideo=document.getElementById('main-video'),$clock=document.getElementById('clock'),$dateDisplay=document.getElementById('date-display'),$exifInfo=document.getElementById('exif-info'),$progressFill=document.getElementById('progress-fill'),$overlay=document.getElementById('overlay'),$btnSettings=document.getElementById('btn-settings'),$progressBar=document.getElementById('progress-bar'); // === WEBSOCKET === - function connectWebSocket(){var proto=location.protocol==='https:'?'wss:':'ws:';wsConn=new WebSocket(proto+'//'+location.host+'/ws');wsConn.onopen=function(){console.log('[Frambe] WebSocket connected');wsConn.send(JSON.stringify({type:'register',role:'frame',persistentId:persistentId,status:isRunning?'playing':(isSleeping?'sleeping':'idle'),config:getCurrentConfig()}));};wsConn.onmessage=function(e){try{var msg=JSON.parse(e.data);if(msg.type==='welcome'){clientId=msg.clientId;console.log('[Frambe] Registered as '+clientId);}else if(msg.type==='command'){handleRemoteCommand(msg.action,msg.payload||{});}}catch(err){}};wsConn.onclose=function(){setTimeout(connectWebSocket,5000);};} - function sendStatus(s){if(wsConn&&wsConn.readyState===WebSocket.OPEN)wsConn.send(JSON.stringify({type:'status',status:s,currentAlbum:selectedAlbumId,config:getCurrentConfig()}));} + function connectWebSocket(){var proto=location.protocol==='https:'?'wss:':'ws:';wsConn=new WebSocket(proto+'//'+location.host+'/ws');wsConn.onopen=function(){console.log('[Frambe] WebSocket connected');wsConn.send(JSON.stringify({type:'register',role:'frame',persistentId:persistentId,status:isRunning?'playing':(isSleeping?'sleeping':'idle'),config:getCurrentConfig(),source:currentSourceDescriptor()}));};wsConn.onmessage=function(e){try{var msg=JSON.parse(e.data);if(msg.type==='hello'||msg.type==='welcome'){clientId=msg.clientId;console.log('[Frambe] Registered as '+clientId);}else if(msg.type==='command'){handleRemoteCommand(msg.action,msg.payload||{});}else if(msg.type==='serverConfig'){applyServerConfig(msg.config||{});}}catch(err){}};wsConn.onclose=function(){setTimeout(connectWebSocket,5000);};} + function sendStatus(s){if(wsConn&&wsConn.readyState===WebSocket.OPEN)wsConn.send(JSON.stringify({type:'status',status:s,currentAlbum:selectedAlbumId,config:getCurrentConfig(),source:currentSourceDescriptor()}));} function getCurrentConfig(){return{slideshowInterval:config.slideshowInterval,showClock:config.showClock,showDate:config.showDate,showExif:config.showExif,showProgress:config.showProgress};} + function currentSourceDescriptor(){if(!selectedSource)return null;return{source:selectedSource,albumId:selectedAlbumId||null,personId:selectedPersonId||null};} + + // === SERVER-CONTROLLED DEFAULTS === + // The server pushes a resolved config (default source, timers, display toggles, sleep + // schedule) for clients that haven't been manually overridden. Applying it here makes + // a freshly opened frame inherit the global settings without any local setup. + function applyServerConfig(sc){ + console.log('[Frambe] Server config received'); + // Display + timer defaults + if('slideshowInterval'in sc)config.slideshowInterval=sc.slideshowInterval; + if('showClock'in sc)config.showClock=sc.showClock; + if('showDate'in sc)config.showDate=sc.showDate; + if('showExif'in sc)config.showExif=sc.showExif; + if('showProgress'in sc)config.showProgress=sc.showProgress; + if(isRunning)applyConfigChange({slideshowInterval:config.slideshowInterval,showClock:config.showClock,showDate:config.showDate,showExif:config.showExif,showProgress:config.showProgress}); + // Sleep schedule state — if the server says we are inside the sleep window, honour it. + if(sc.sleep){if(sc.sleep.sleeping&&!isSleeping)goToSleep();else if(sc.sleep.sleeping===false&&isSleeping)wakeUp();} + // Default photo source — only auto-start if nothing is playing yet and no URL/local choice was made. + if(sc.source&&sc.source.source&&!isRunning&&!urlDriven&&!selectedSource){ + selectedSource=sc.source.source;selectedAlbumId=sc.source.albumId||null;selectedPersonId=sc.source.personId||null; + console.log('[Frambe] Auto-start from server default: '+selectedSource); + if(!(sc.sleep&&sc.sleep.sleeping))doStartSlideshow(); + } + } + function handleRemoteCommand(action,payload){console.log('[Frambe] Remote: '+action);switch(action){case'setSource':selectedSource=payload.source;selectedAlbumId=payload.albumId||null;selectedPersonId=payload.personId||null;if(isSleeping)wakeUp();if(isRunning){clearTimeout(slideshowTimer);stopVideo();}doStartSlideshow();break;case'start':if(isSleeping)wakeUp();if(!isRunning&&selectedSource)doStartSlideshow();break;case'stop':if(isRunning)exitSlideshowInternal();sendStatus('idle');break;case'next':if(isRunning)showNextAsset();break;case'prev':if(isRunning)showPrevAsset();break;case'sleep':goToSleep();break;case'wake':wakeUp();break;case'refresh':location.reload();break;case'setConfig':applyConfigChange(payload);break;}} function goToSleep(){isSleeping=true;document.body.style.background='#000';if($slideshowScreen)$slideshowScreen.style.display='none';if($setupScreen)$setupScreen.style.display='none';var s=document.getElementById('sleep-overlay');if(!s){s=document.createElement('div');s.id='sleep-overlay';s.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;background:#000;z-index:9999;';document.body.appendChild(s);}s.style.display='block';if(isRunning){clearTimeout(slideshowTimer);stopVideo();}sendStatus('sleeping');} function wakeUp(){isSleeping=false;document.body.style.background='';var s=document.getElementById('sleep-overlay');if(s)s.style.display='none';if(isRunning)$slideshowScreen.style.display='block';else $setupScreen.style.display='flex';sendStatus(isRunning?'playing':'idle');}