452 lines
15 KiB
HTML
452 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1"
|
||
/>
|
||
<title>Media Dashboard</title>
|
||
<link rel="icon" type="image/x-icon" href="../static/images/favicon.ico" />
|
||
<link rel="stylesheet" href="../static/css/styles.css" />
|
||
<link rel="stylesheet" href="../static/fontawesome/css/all.min.css" />
|
||
<style>
|
||
/* High-contrast text everywhere */
|
||
body,
|
||
.content,
|
||
h1, h2, h3, h4, h5, h6,
|
||
p, label, .status,
|
||
.checkbox-inline,
|
||
.container,
|
||
.form-group,
|
||
.form-group * {
|
||
color: #1f2937; /* gray-800 */
|
||
}
|
||
|
||
*{ box-sizing: border-box; }
|
||
html, body{ height: 100%; }
|
||
body{
|
||
margin: 0;
|
||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||
line-height: 1.55;
|
||
/* Page/background image/color is controlled by your existing CSS; unchanged */
|
||
}
|
||
|
||
#navbar{ position: sticky; top: 0; z-index: 50; }
|
||
|
||
.content-wrapper{
|
||
position: relative; /* anchor for the underlay image */
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 16px;
|
||
z-index: 1; /* keep main content above the underlay */
|
||
}
|
||
@media (min-width: 980px){
|
||
.content-wrapper{ padding: 24px; }
|
||
}
|
||
|
||
.content{
|
||
position: relative;
|
||
z-index: 2; /* ensure UI is above the image */
|
||
display: flex;
|
||
flex-direction: column; /* stack containers vertically */
|
||
align-items: center; /* center containers horizontally */
|
||
gap: 20px;
|
||
}
|
||
|
||
/* Ensure the main title spans full width and is centered above the panels */
|
||
.content > h1{
|
||
flex-basis: 100%;
|
||
text-align: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
/* Underlay image: left-aligned, same size, no layout impact */
|
||
.side-img{
|
||
position: absolute;
|
||
left: 0;
|
||
top: 110px; /* adjust as you like */
|
||
width: 230px; /* keep current size */
|
||
height: auto;
|
||
z-index: 0; /* behind everything */
|
||
pointer-events: none; /* clicks go through */
|
||
opacity: 1; /* fully visible; change if you want subtler */
|
||
}
|
||
|
||
h1{
|
||
margin: 0 0 8px;
|
||
font-size: clamp(1.6rem, 2.2vw, 2.2rem);
|
||
color: #333; /* explicit, high-contrast */
|
||
}
|
||
h2{
|
||
margin: 0 0 10px;
|
||
font-size: clamp(1.1rem, 1.8vw, 1.4rem);
|
||
color: #333;
|
||
}
|
||
|
||
/* Card containers – background color kept EXACTLY as before */
|
||
.container{
|
||
width: 100%;
|
||
max-width: 420px;
|
||
margin: 0 0 16px;
|
||
padding: 16px;
|
||
border: 2px solid #ccc;
|
||
border-radius: 10px;
|
||
background-color: #ffffffb6; /* unchanged */
|
||
position: relative; /* creates its own stacking context above image */
|
||
z-index: 1;
|
||
}
|
||
|
||
.form-group{ margin-bottom: 14px; }
|
||
.form-group label{ display: block; margin-bottom: 6px; font-weight: 600; }
|
||
.form-group input,
|
||
.form-group select,
|
||
.form-group textarea{
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
font-size: 1rem;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.checkbox-row{
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.checkbox-inline{
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 600;
|
||
}
|
||
/* utility: push an inline checkbox/label to the far right inside a flex row */
|
||
.checkbox-inline.right { margin-left: auto; }
|
||
|
||
.button-group{
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-start;
|
||
margin: 12px 0 6px;
|
||
}
|
||
|
||
.button-row{
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.button-row .controls{ display:flex; gap:10px; }
|
||
.button-row .actions{ margin-left: auto; }
|
||
|
||
.btn{
|
||
appearance: none;
|
||
border: 1px solid transparent;
|
||
border-radius: 8px;
|
||
padding: 10px 16px;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
color: #fff; /* keep buttons readable */
|
||
background: #1e40af; /* blue-800 */
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-decoration: none;
|
||
}
|
||
.btn:hover{ background: #15327f; }
|
||
.btn:focus-visible{
|
||
outline: 3px solid #2563eb;
|
||
outline-offset: 2px;
|
||
}
|
||
.btn[disabled]{ opacity: 0.6; cursor: not-allowed; }
|
||
|
||
.status{
|
||
margin-top: 8px;
|
||
font-size: 0.98rem;
|
||
min-height: 1.2em;
|
||
}
|
||
|
||
.input-label{ text-align: left; }
|
||
|
||
.form-group textarea{
|
||
resize: none;
|
||
overflow-wrap: break-word;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
/* Optional: make sure the underlay doesn't collide on very small screens */
|
||
@media (max-width: 480px){
|
||
.side-img{ top: 140px; width: 190px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="navbar"></div>
|
||
<script>
|
||
fetch('/static/html/nav.html')
|
||
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="container" role="alert">Navigation failed to load.</div>'; });
|
||
</script>
|
||
|
||
<div class="background-image"></div>
|
||
|
||
<main class="content-wrapper">
|
||
<!-- Underlay image (beneath everything, left-aligned, fixed size) -->
|
||
<img src="/static/images/helio-posh.png" alt="Helio Posh" class="side-img" />
|
||
|
||
<div class="content">
|
||
<h1>Media Dashboard</h1>
|
||
|
||
<section class="container" aria-labelledby="playlist-heading">
|
||
<h2 id="playlist-heading">Playlist Loop</h2>
|
||
|
||
<div class="checkbox-row">
|
||
<label class="checkbox-inline" for="autoPlayAtBoot">
|
||
<input type="checkbox" id="autoPlayAtBoot" name="autoPlayAtBoot">
|
||
Autostart @ Boot
|
||
</label>
|
||
|
||
<label class="checkbox-inline" for="saveSettings">
|
||
<input type="checkbox" id="saveSettings" name="saveSettings">
|
||
Save
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="input-label" for="mediaLocation">Media Sources</label>
|
||
<select id="mediaLocation" name="mediaLocation" aria-describedby="mediaHelp">
|
||
<option value="USB">No Media Available</option>
|
||
</select>
|
||
<div id="mediaHelp" class="sr-only">Choose a folder to loop through images.</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="input-label" for="imageDuration">Image Duration (secs)</label>
|
||
<input type="number" id="imageDuration" name="imageDuration" min="1" max="60" inputmode="numeric">
|
||
</div>
|
||
|
||
<div class="button-group" role="group" aria-label="Playlist loop controls">
|
||
<button class="btn" id="btnStartLoop" onclick="startMediaLoop()">
|
||
<i class="fa fa-play" aria-hidden="true"></i> Start
|
||
</button>
|
||
<button class="btn" id="btnStopLoop" onclick="stopMediaLoop()">
|
||
<i class="fa fa-stop" aria-hidden="true"></i> Stop
|
||
</button>
|
||
</div>
|
||
|
||
<div class="status" id="mediaLoopStatus" role="status" aria-live="polite">status</div>
|
||
</section>
|
||
|
||
<section class="container" aria-labelledby="gallery-heading">
|
||
<h2 id="gallery-heading">Web Gallery</h2>
|
||
|
||
<div class="checkbox-row" style="justify-content:flex-start;">
|
||
<label class="checkbox-inline" for="autoStart">
|
||
<input type="checkbox" id="autoStart" name="autoStart">
|
||
Autostart
|
||
</label>
|
||
|
||
<label class="checkbox-inline right" for="saveWebSettings">
|
||
<input type="checkbox" id="saveWebSettings" name="saveWebSettings">
|
||
Save
|
||
</label>
|
||
</div>
|
||
|
||
|
||
<div class="form-group">
|
||
<label class="input-label" for="galleryURL">URL</label>
|
||
<textarea id="galleryURL" name="galleryURL" rows="3" placeholder="https://yahoo.com"></textarea>
|
||
</div>
|
||
|
||
<div class="button-row" role="group" aria-label="Web gallery controls">
|
||
<div class="controls">
|
||
<button class="btn" id="btnStartWeb" onclick="startWebGallery()">
|
||
<i class="fa fa-play" aria-hidden="true"></i> Start
|
||
</button>
|
||
<button class="btn" id="btnStopWeb" onclick="stopWebGallery()">
|
||
<i class="fa fa-stop" aria-hidden="true"></i> Stop
|
||
</button>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn" type="button" onclick="clearURL()">
|
||
<i class="fa fa-eraser" aria-hidden="true"></i> Clear
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status" id="webGalleryStatus" role="status" aria-live="polite">status</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
function setBusy(el, busy){
|
||
if (!el) return;
|
||
el.disabled = !!busy;
|
||
if (busy) el.setAttribute('aria-busy','true'); else el.removeAttribute('aria-busy');
|
||
}
|
||
function setText(id, text){
|
||
const el = document.getElementById(id);
|
||
if (el) el.textContent = text || '';
|
||
}
|
||
|
||
async function loadMediaSources(){
|
||
try{
|
||
const res = await fetch('../get_media_sources');
|
||
const data = await res.json();
|
||
const select = document.getElementById('mediaLocation');
|
||
if (!select) return;
|
||
// Clear existing
|
||
select.innerHTML = '';
|
||
const arr = Array.isArray(data?.folders) ? data.folders : [];
|
||
if (arr.length === 0){
|
||
const opt = document.createElement('option');
|
||
opt.value = 'USB';
|
||
opt.text = 'No Media Available';
|
||
select.add(opt);
|
||
return;
|
||
}
|
||
arr.forEach(m => {
|
||
const opt = document.createElement('option');
|
||
opt.text = m.folder_name_display;
|
||
opt.value = m.folder_path;
|
||
select.add(opt);
|
||
});
|
||
}catch(err){
|
||
console.error('Error loading media sources:', err);
|
||
}
|
||
}
|
||
|
||
async function loadLastUsedData(){
|
||
try{
|
||
const res = await fetch('../get_screen_settings');
|
||
const data = await res.json();
|
||
|
||
// Only default if undefined (avoid forcing true when false)
|
||
const apb = (data.autoPlayAtBoot === undefined) ? true : !!data.autoPlayAtBoot;
|
||
const dur = (data.imageDuration === undefined) ? 5 : Number(data.imageDuration);
|
||
const as = (data.autoStart === undefined) ? true : !!data.autoStart;
|
||
const url = (data.url === undefined) ? 'google.com' : String(data.url);
|
||
|
||
document.getElementById('autoPlayAtBoot').checked = apb;
|
||
document.getElementById('imageDuration').value = dur;
|
||
document.getElementById('autoStart').checked = as;
|
||
document.getElementById('galleryURL').value = url;
|
||
document.getElementById('saveSettings').checked = false;
|
||
}catch(err){
|
||
console.error('Error loading screen settings:', err);
|
||
}
|
||
}
|
||
|
||
async function startMediaLoop(){
|
||
const mediaLocation = document.getElementById('mediaLocation').value;
|
||
const imageDuration = document.getElementById('imageDuration').value;
|
||
const autoPlayAtBoot = document.getElementById('autoPlayAtBoot').checked;
|
||
const saveSettings = document.getElementById('saveSettings').checked;
|
||
|
||
// Reset the save toggle after reading
|
||
document.getElementById('saveSettings').checked = false;
|
||
|
||
const btnStart = document.getElementById('btnStartLoop');
|
||
const btnStop = document.getElementById('btnStopLoop');
|
||
setBusy(btnStart, true); setBusy(btnStop, true);
|
||
setText('mediaLoopStatus', 'Starting media loop…');
|
||
|
||
try{
|
||
const res = await fetch('../start_media_loop', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ mediaLocation, imageDuration, autoPlayAtBoot, saveSettings })
|
||
});
|
||
const data = await res.json();
|
||
setText('mediaLoopStatus', data.message || 'Started.');
|
||
}catch(err){
|
||
console.error('Error during start media loop:', err);
|
||
setText('mediaLoopStatus', 'Error during start media loop.');
|
||
}finally{
|
||
setBusy(btnStart, false); setBusy(btnStop, false);
|
||
}
|
||
}
|
||
|
||
async function stopMediaLoop(){
|
||
const btnStart = document.getElementById('btnStartLoop');
|
||
const btnStop = document.getElementById('btnStopLoop');
|
||
setBusy(btnStart, true); setBusy(btnStop, true);
|
||
setText('mediaLoopStatus', 'Stopping media loop…');
|
||
try{
|
||
const res = await fetch('../stop_media_loop', { method: 'POST' });
|
||
const data = await res.json();
|
||
setText('mediaLoopStatus', data.message || 'Stopped.');
|
||
}catch(err){
|
||
console.error('Error during stop media loop:', err);
|
||
setText('mediaLoopStatus', 'Error during stop media loop.');
|
||
}finally{
|
||
setBusy(btnStart, false); setBusy(btnStop, false);
|
||
}
|
||
}
|
||
|
||
async function startWebGallery(){
|
||
const autoStart = document.getElementById('autoStart').checked;
|
||
const url = document.getElementById('galleryURL').value;
|
||
|
||
const btnStart = document.getElementById('btnStartWeb');
|
||
const btnStop = document.getElementById('btnStopWeb');
|
||
const saveWebSettings = document.getElementById('saveWebSettings').checked;
|
||
|
||
setBusy(btnStart, true); setBusy(btnStop, true);
|
||
setText('webGalleryStatus', 'Starting web gallery…');
|
||
|
||
try{
|
||
const res = await fetch('../start_web_gallery', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ url, autoStart, saveWebSettings })
|
||
});
|
||
const data = await res.json();
|
||
setText('webGalleryStatus', data.message || 'Started.');
|
||
}catch(err){
|
||
console.error('Error during start web gallery:', err);
|
||
setText('webGalleryStatus', 'Error during start web gallery.');
|
||
}finally{
|
||
setBusy(btnStart, false); setBusy(btnStop, false);
|
||
document.getElementById('saveWebSettings').checked = false;
|
||
}
|
||
}
|
||
|
||
async function stopWebGallery(){
|
||
const btnStart = document.getElementById('btnStartWeb');
|
||
const btnStop = document.getElementById('btnStopWeb');
|
||
setBusy(btnStart, true); setBusy(btnStop, true);
|
||
setText('webGalleryStatus', 'Stopping web gallery…');
|
||
|
||
try{
|
||
const res = await fetch('../stop_web_gallery', { method: 'POST' });
|
||
const data = await res.json();
|
||
setText('webGalleryStatus', data.message || 'Stopped.');
|
||
}catch(err){
|
||
console.error('Error during stop web gallery:', err);
|
||
setText('webGalleryStatus', 'Error during stop web gallery.');
|
||
}finally{
|
||
setBusy(btnStart, false); setBusy(btnStop, false);
|
||
}
|
||
}
|
||
|
||
function clearURL(){ document.getElementById('galleryURL').value = ''; }
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadLastUsedData();
|
||
loadMediaSources();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|