atadashOrig/templates/index.html
2025-10-02 15:45:10 -07:00

452 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>