hannabox/web/index.html

187 lines
7.6 KiB
HTML

<!DOCTYPE HTML>
<html>
<head>
<title>HannaBox</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css" type="text/css" media="all" />
</head>
<body>
<header class="topbar">
<div class="brand">
<div>
<h1>HannaBox</h1>
</div>
</div>
<div class="status" id="state"></div>
</header>
<main class="container">
<section class="player-card">
<div class="artwork" id="artwork">
<!-- Placeholder artwork -->
<svg viewBox="0 0 100 100" class="art-svg" aria-hidden="true">
<rect x="0" y="0" width="100" height="100" fill="#e9f0ff"></rect>
<circle cx="50" cy="40" r="18" fill="#cfe0ff"></circle>
<rect x="20" y="65" width="60" height="8" rx="2" fill="#cfe0ff"></rect>
</svg>
</div>
<div class="controls">
<div class="big-title" id="stateTitle"></div>
<div class="control-row">
<button class="icon-btn prev-button" title="Previous" onclick="simpleGetCall('previous');">
<svg width="36" height="36" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M11 19V5L4 12l7 7zM20 5v14h-2V5h2z"/>
</svg>
</button>
<button class="icon-btn play-button" title="Play / Pause" onclick="simpleGetCall('toggleplaypause');">
<svg class="play-icon" width="40" height="40" viewBox="0 0 24 24" fill="currentColor"><path d="M5 3v18l15-9L5 3z"/></svg>
<svg class="pause-icon" width="40" height="40" viewBox="0 0 24 24" fill="currentColor" style="display:none"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
</button>
<button class="icon-btn next-button" title="Next" onclick="simpleGetCall('next');">
<svg width="36" height="36" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M13 5v14l7-7-7-7zM4 5v14h2V5H4z"/>
</svg>
</button>
</div>
<div class="slider-row">
<div class="slidecontainer">
<input name="progress" type="range" min="0" max="100" value="0" class="slider" id="progressSlider"
onchange="postValue('progress',document.getElementById('progressSlider').value);lastChange = Date.now();userIsInteracting = false;"
oninput="userIsInteracting = true;">
<div class="time-row">
<span id="progressLabel">0</span>
<span class="time-sep">/</span>
<span id="progressMax">0</span>
</div>
</div>
<div class="volumecontainer">
<label for="volumeSlider">Vol</label>
<input name="volume" type="range" min="0" max="15" value="7" class="slider" id="volumeSlider"
onchange="postValue('volume',document.getElementById('volumeSlider').value);lastChange = Date.now()">
</div>
</div>
</div>
</section>
<aside class="playlist">
<div class="playlist-header">
<h2>Playlist</h2>
<button class="action-btn small" onclick="location.reload()">Refresh</button>
</div>
<div class="playlist-container">
%DIRECTORY%
</div>
<div class="manager-toggle">
<button id="toggleFileManagerButton" class="action-btn" onclick="toggleFileManager()">Toggle Manager</button>
</div>
<div id="fileManager" class="file-manager" style="display:none;">
<h3>Manager</h3>
<div class="info-row">
<div id="voltage"></div>
<div id="uid"></div>
<div id="heap"></div>
</div>
<h4>Upload File</h4>
<form id="uploadForm" class="form" method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="data" id="uploadFile" accept=".mp3,.wav,.flac,.m4a,.ogg"/>
<input type="submit" name="upload" value="Upload" title="Upload Audio File" id="uploadButton" class="action-btn"/>
<div id="uploadStatus"></div>
<div id="uploadProgress" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<span id="progressText">0</span>
</div>
</form>
<h4>Edit RFID Mapping</h4>
<p class="hint">Hint: Use a folder or filename, not the absolute file path!</p>
<div class="mapping-list">%MAPPING%</div>
<form id="editMappingForm" class="form form-grid">
<div>
<label for="rfid">RFID:</label>
<input type="text" id="rfid" name="rfid" required>
</div>
<div>
<label for="song">Song:</label>
<input type="text" id="song" name="song" required>
</div>
<div>
<label for="mode">Mode:</label>
<select id="mode" name="mode">
<option value="s">Single (play selected song / file)</option>
<option value="f">Folder (play selected folder, then stop)</option>
<option value="c">Continuous (continuous playback / loop folder)</option>
</select>
</div>
<button type="button" class="action-btn" style="grid-column: 1 / -1;" onclick="editMapping()">Update Mapping</button>
</form>
<h4>Move / Rename File</h4>
<form class="form form-grid">
<div>
<label for="moveFrom">From:</label>
<input type="text" id="moveFrom" placeholder="/oldname.mp3"/>
</div>
<div>
<label for="moveTo">To:</label>
<input type="text" id="moveTo" placeholder="/newname.mp3"/>
</div>
<button type="button" class="action-btn" style="grid-column: 1 / -1;" onclick="moveFile()">Move/Rename</button>
</form>
<h4>Delete File</h4>
<form class="form form-grid">
<div>
<label for="deleteFileName">Filename:</label>
<input type="text" id="deleteFileName" placeholder="/song.mp3"/>
</div>
<button type="button" class="action-btn" style="grid-column: 1 / -1;" onclick="deleteFileOnServer()">Delete</button>
</form>
</div>
</aside>
</main>
<footer class="footer">
<div>Built on ESP32 • hannabox</div>
</footer>
<script src="script.js"></script>
<script>
// Keep play/pause icon in sync with the existing class-based logic
// Toggle visibility of play/pause SVGs based on .paused class from displayState()
function syncPlayIcon() {
var btn = document.querySelector('.play-button');
if (!btn) return;
var playIcon = btn.querySelector('.play-icon');
var pauseIcon = btn.querySelector('.pause-icon');
if (btn.classList.contains('paused')) {
playIcon.style.display = 'none';
pauseIcon.style.display = '';
} else {
playIcon.style.display = '';
pauseIcon.style.display = 'none';
}
}
// Observe mutations to class attribute on play-button to update icons
var observer = new MutationObserver(syncPlayIcon);
var playBtn = document.querySelector('.play-button');
if (playBtn) observer.observe(playBtn, { attributes: true, attributeFilter: ['class'] });
// Also call regularly to catch updates
setInterval(syncPlayIcon, 800);
</script>
</body>
</html>