|
730 | 730 | box-sizing: border-box; |
731 | 731 | } |
732 | 732 |
|
| 733 | + /* Unlocked is the neutral/default state -> look like the other (inactive) control buttons, |
| 734 | + so it no longer stands out as if it were "active". Only "locked" gets an accent colour. */ |
733 | 735 | .cp-lock-button.unlocked { |
734 | | - color: var(--cp-cyan); |
735 | | - border: 1px solid rgba(0, 240, 255, .4); |
736 | | - background: rgba(0, 240, 255, .05); |
737 | | - box-shadow: 0 0 8px rgba(0, 240, 255, .1); |
| 736 | + color: var(--cp-dim); |
| 737 | + border: 1px solid var(--cp-border); |
| 738 | + background: rgba(22, 33, 60, .2); |
| 739 | + box-shadow: 0 0 6px rgba(0, 0, 0, .2); |
738 | 740 | } |
739 | 741 |
|
740 | 742 | .cp-lock-button.unlocked:hover { |
|
1553 | 1555 | </div> |
1554 | 1556 | </div> |
1555 | 1557 | </nav> |
| 1558 | + <!-- Modal dialog: detailed info about the currently playing track (ID3, size, RFID, ...) --> |
| 1559 | + <div class="modal fade" data-bs-backdrop="static" id="modalTrackInfo" role="dialog"> |
| 1560 | + <div class="modal-dialog modal-dialog-scrollable" role="document"> |
| 1561 | + <div class="modal-content"> |
| 1562 | + <div class="modal-header"> |
| 1563 | + <h5 class="modal-title" data-i18n="control.trackinfo.title"></h5> |
| 1564 | + </div> |
| 1565 | + <div class="modal-body" id="modalTrackInfoContent"></div> |
| 1566 | + <div class="modal-footer"> |
| 1567 | + <button type="button" class="btn btn-primary" data-bs-dismiss="modal" data-i18n="ok"></button> |
| 1568 | + </div> |
| 1569 | + </div> |
| 1570 | + </div> |
| 1571 | + </div> |
1556 | 1572 | <!-- Modal dialog for info / shutdown / restart--> |
1557 | 1573 | <div class="modal fade bd-info-modal-xl" data-bs-backdrop="static" id="modalInfo" role="dialog"> |
1558 | 1574 | <div class="modal-dialog modal-xl modal-dialog-scrollable" role="document"> |
@@ -1934,7 +1950,13 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5> |
1934 | 1950 | </div> |
1935 | 1951 | <br /> |
1936 | 1952 | <div class="mb-3 col-md-12"> |
1937 | | - <legend data-i18n="control.current"></legend> |
| 1953 | + <legend class="d-flex align-items-center justify-content-between" style="border-bottom:none;"> |
| 1954 | + <span data-i18n="control.current"></span> |
| 1955 | + <button type="button" class="btn btn-sm cp-control-button" id="btn-track-info" |
| 1956 | + onclick="showTrackInfo()" data-i18n="[title]control.trackinfo.title"> |
| 1957 | + <span class="fas fa-circle-info"></span> |
| 1958 | + </button> |
| 1959 | + </legend> |
1938 | 1960 | <div id="track"></div> |
1939 | 1961 | <div id="trackmeta" class="text-secondary small mb-1"></div> |
1940 | 1962 | <div id="trackProgressDiv" class="progress progress-sm"> |
@@ -6433,6 +6455,42 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5> |
6433 | 6455 | } |
6434 | 6456 | } |
6435 | 6457 |
|
| 6458 | + // Detailed info dialog for the currently playing track (ID3 tags, size, RFID tag, ...). |
| 6459 | + async function showTrackInfo() { |
| 6460 | + let d; |
| 6461 | + try { d = await (await fetch("http://" + host + "/currenttrack")).json(); } catch (e) { d = null; } |
| 6462 | + const el = document.getElementById('modalTrackInfoContent'); |
| 6463 | + const t = (k) => i18next.t('control.trackinfo.' + k); |
| 6464 | + if (!d || !d.active) { |
| 6465 | + el.innerHTML = '<p class="text-secondary mb-0">' + escapeHtml(t('none')) + '</p>'; |
| 6466 | + new bootstrap.Modal(document.getElementById('modalTrackInfo')).show(); |
| 6467 | + return; |
| 6468 | + } |
| 6469 | + const fmtBytes = (n) => { |
| 6470 | + n = Number(n); if (!isFinite(n) || n <= 0) return null; |
| 6471 | + const u = ['B', 'KB', 'MB', 'GB']; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } |
| 6472 | + return (i === 0 ? n : (n < 10 ? n.toFixed(1) : Math.round(n))) + ' ' + u[i]; |
| 6473 | + }; |
| 6474 | + const rows = []; |
| 6475 | + const add = (labelKey, value) => { if (value === undefined || value === null || value === '') return; rows.push('<tr><th scope="row">' + t(labelKey) + '</th><td class="text-break">' + escapeHtml(String(value)) + '</td></tr>'); }; |
| 6476 | + add('songTitle', d.title); |
| 6477 | + add('artist', d.artist); |
| 6478 | + add('album', d.album); |
| 6479 | + if (d.isWebstream) add('type', t('webstream')); |
| 6480 | + add('path', d.path); |
| 6481 | + add('size', fmtBytes(d.size)); |
| 6482 | + if (d.totalTracks > 1) add('track', d.trackNumber + ' / ' + d.totalTracks); |
| 6483 | + if (d.playMode) add('playmode', i18next.t('files.rfid.playmode.mode.' + d.playMode)); |
| 6484 | + if (d.duration > 0) add('position', secondsToTime(d.elapsed || 0) + ' / ' + secondsToTime(d.duration)); |
| 6485 | + if (d.rfidTag && !/^0+$/.test(d.rfidTag)) add('rfid', d.rfidTag); |
| 6486 | + let html = ''; |
| 6487 | + // show the cover thumbnail when one is embedded |
| 6488 | + html += '<div class="text-center mb-3"><img src="http://' + host + '/cover?' + Date.now() + '" alt="" style="max-width:160px;max-height:160px;border-radius:6px" onerror="this.style.display=\'none\'"></div>'; |
| 6489 | + html += '<table class="table table-sm table-borderless cp-info-table mb-0"><tbody>' + rows.join('') + '</tbody></table>'; |
| 6490 | + el.innerHTML = html; |
| 6491 | + new bootstrap.Modal(document.getElementById('modalTrackInfo')).show(); |
| 6492 | + } |
| 6493 | + |
6436 | 6494 | async function fetchInfo() { |
6437 | 6495 | let info = await (await fetch("http://" + host + "/info")).json(); |
6438 | 6496 | console.log(info); |
|
0 commit comments