191 lines
7.4 KiB
HTML
191 lines
7.4 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}diora — radio player{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<!-- ===== NOW PLAYING BAR ===== -->
|
|
<section class="now-playing-bar" id="now-playing-bar">
|
|
<div class="now-playing-info">
|
|
<span class="now-playing-station" id="now-playing-station">— no station —</span>
|
|
<span class="now-playing-track" id="now-playing-track"></span>
|
|
</div>
|
|
<div class="now-playing-controls">
|
|
<button class="btn btn-play" id="play-stop-btn" onclick="togglePlayStop()" style="display:none;">▶ Play</button>
|
|
<label class="volume-label">
|
|
<span>vol</span>
|
|
<input type="range" id="volume" min="0" max="100" value="80" class="volume-slider">
|
|
</label>
|
|
<button class="btn btn-save" id="save-station-btn" style="display:none;" onclick="saveCurrentStation()">★ Save</button>
|
|
<button class="btn-icon" id="dnd-btn" onclick="toggleDND()" title="Focus mode (hides UI, press Esc to exit)">⊙</button>
|
|
</div>
|
|
<div class="timer-widget" id="timer-widget">
|
|
<span class="timer-phase" id="timer-phase-label">focus</span>
|
|
<span class="timer-display" id="timer-display">25:00</span>
|
|
<button class="btn-icon" id="timer-toggle-btn" onclick="toggleTimer()" title="Start/pause timer">▶</button>
|
|
<button class="btn-icon" id="timer-reset-btn" onclick="resetTimer()" title="Reset timer">↺</button>
|
|
<span class="focus-today" id="focus-today-widget" style="display:none;"></span>
|
|
<button class="btn-icon dnd-only" id="dnd-light-btn" onclick="toggleDNDLight()" title="Toggle black background">💡</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ===== AFFILIATE / TRACK INFO ===== -->
|
|
<section class="affiliate-section" id="affiliate-section" style="display:none;">
|
|
<img class="affiliate-artwork" id="affiliate-artwork" src="" alt="Album art">
|
|
<div class="affiliate-info">
|
|
<div class="affiliate-track" id="affiliate-track-name"></div>
|
|
<div class="affiliate-artist" id="affiliate-artist-name"></div>
|
|
<div class="affiliate-album" id="affiliate-album-name"></div>
|
|
<a class="btn btn-amazon" id="affiliate-amazon-link" href="#" target="_blank" rel="noopener noreferrer">
|
|
Buy on Amazon Music
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ===== TABS ===== -->
|
|
<div class="tabs" id="tabs">
|
|
<button class="tab-btn active" onclick="showTab('search')">Search</button>
|
|
<button class="tab-btn" onclick="showTab('saved')">Saved</button>
|
|
<button class="tab-btn" onclick="showTab('history')">History</button>
|
|
<button class="tab-btn" onclick="showTab('focus')">Focus</button>
|
|
</div>
|
|
|
|
<!-- ===== SEARCH TAB ===== -->
|
|
<section class="tab-panel" id="tab-search">
|
|
<div class="search-bar">
|
|
<input type="text" id="search-input" class="search-input" placeholder="Search radio-browser.info…" onkeydown="if(event.key==='Enter') doSearch()">
|
|
<button class="btn" onclick="doSearch()">Search</button>
|
|
</div>
|
|
<div class="mood-chips" id="mood-chips"></div>
|
|
<div id="curated-lists" class="curated-lists-container"></div>
|
|
<div id="search-status" class="status-msg"></div>
|
|
<table class="data-table" id="search-results-table" style="display:none;">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Bitrate</th>
|
|
<th>Country</th>
|
|
<th>Tags</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="search-results-body"></tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<!-- ===== SAVED TAB ===== -->
|
|
<section class="tab-panel" id="tab-saved" style="display:none;">
|
|
{% if user.is_authenticated %}
|
|
<div id="recommendations" class="recommendations-section">
|
|
<!-- populated by JS -->
|
|
</div>
|
|
<div class="import-bar">
|
|
<label class="btn btn-sm" for="m3u-file-input" title="Import .m3u / .m3u8 from the desktop app">
|
|
⇧ Import M3U
|
|
</label>
|
|
<input type="file" id="m3u-file-input" accept=".m3u,.m3u8" style="display:none;" onchange="importM3U(this)">
|
|
<span id="import-status" class="muted"></span>
|
|
</div>
|
|
<table class="data-table" id="saved-table">
|
|
<thead>
|
|
<tr>
|
|
<th>★</th>
|
|
<th>Name</th>
|
|
<th>Bitrate</th>
|
|
<th>Country</th>
|
|
<th title="Notes">✎</th>
|
|
<th></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="saved-tbody">
|
|
{% for station in saved_stations %}
|
|
<tr id="saved-row-{{ station.id }}" data-id="{{ station.id }}" data-url="{{ station.url }}" data-name="{{ station.name }}">
|
|
<td>
|
|
<button class="btn-icon fav-btn {% if station.is_favorite %}active{% endif %}"
|
|
onclick="toggleFav({{ station.id }})"
|
|
title="Toggle favorite">★</button>
|
|
</td>
|
|
<td class="station-name-cell">{{ station.name }}</td>
|
|
<td>{{ station.bitrate }}</td>
|
|
<td>{{ station.country }}</td>
|
|
<td class="notes-cell" onclick="editNotes({{ station.id }}, this.textContent.trim())" title="{{ station.notes|default:'' }}" style="cursor:pointer; color:#666; max-width:120px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">{{ station.notes }}</td>
|
|
<td>
|
|
<button class="btn btn-sm"
|
|
onclick="playStation('{{ station.url }}', '{{ station.name|escapejs }}', {{ station.id }})">
|
|
▶ Play
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-danger"
|
|
onclick="removeStation({{ station.id }})">
|
|
Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr id="saved-empty-row"><td colspan="7" class="empty-msg">No saved stations yet.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="auth-prompt">
|
|
<a href="{% url 'login' %}">Log in</a> or <a href="{% url 'register' %}">register</a>
|
|
to save stations and sync across devices.
|
|
</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<!-- ===== HISTORY TAB ===== -->
|
|
<section class="tab-panel" id="tab-history" style="display:none;">
|
|
<table class="data-table" id="history-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Station</th>
|
|
<th>Track</th>
|
|
<th>♬</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="history-tbody">
|
|
{% for entry in history %}
|
|
<tr data-id="{{ entry.id }}">
|
|
<td class="history-time">{{ entry.played_at|slice:":16"|cut:"T" }}</td>
|
|
<td>{{ entry.station_name }}</td>
|
|
<td>{{ entry.track }}</td>
|
|
<td>{% if entry.scrobbled %}<span title="Scrobbled to Last.fm">✓</span>{% endif %}</td>
|
|
<td><button class="btn-delete-history" onclick="deleteHistoryEntry({{ entry.id }}, this)" title="Remove">✕</button></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr id="history-empty-row"><td colspan="5" class="empty-msg">No history yet.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<!-- ===== FOCUS TAB ===== -->
|
|
<section class="tab-panel" id="tab-focus" style="display:none;">
|
|
<table class="data-table" id="focus-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Station</th>
|
|
<th>Duration</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="focus-tbody">
|
|
<tr><td colspan="3" class="empty-msg">No focus sessions yet. Start the timer!</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Pass Django context into JS
|
|
const INITIAL_SAVED = {{ saved_stations|safe }};
|
|
const IS_AUTHENTICATED = {{ user.is_authenticated|yesno:"true,false" }};
|
|
</script>
|
|
<script src="/static/js/app.js"></script>
|
|
{% endblock %}
|