mirror of
https://codeberg.org/armin/noiz.git
synced 2026-09-01 05:50:52 +02:00
Add tag editor with 10 fields in 2-column layout and footer status display
- Add tag editor overlay/modal with Name, Artist, Album, Genre, Year, Track No., Comment, Composer, URL, and Encoded By fields in 2-column grid - Add Info button to open tag editor - Add footer status display with success/error messaging - Extend track model with comment, composer, urlTag, encodedBy fields - Rename Track field to Track No.
This commit is contained in:
parent
e85de87728
commit
1a933585d2
1 changed files with 126 additions and 1 deletions
127
index.html
127
index.html
|
|
@ -17,7 +17,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
|||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.brand { font-size: 15px; font-weight: 700; color: #6b7089; letter-spacing: 1px; user-select: none; }
|
||||
.brand span { color: #6b7089; }
|
||||
footer { display: flex !important; align-items: center; gap: 16px; padding: 10px 24px; background: linear-gradient(to bottom, #1e2132, #161821); border-top: 1px solid #272c42; font-size: 14px; color: #6b7089; flex-shrink: 0; }
|
||||
footer { display: flex !important; align-items: center; gap: 16px; padding: 10px 24px; background: linear-gradient(to bottom, #1e2132, #161821); border-top: 1px solid #272c42; font-size: 14px; color: #6b7089; flex-shrink: 0; position: relative; }
|
||||
footer .brand { margin-left: auto; }
|
||||
.content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.pinned { flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); padding: 16px 24px; display: flex; flex-direction: row; gap: 20px; align-items: center; }
|
||||
|
|
@ -38,6 +38,14 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
|
|||
#noiz-overlay { position: fixed; opacity: 0.75; inset: 0; display: flex; justify-content: center; align-items: center; background: transparent; pointer-events: none; z-index: 9999; animation: fadeout 750ms ease 750ms forwards; }
|
||||
#noiz-overlay img { max-width: 50vw; max-height: 50vh; filter: drop-shadow(0 8px 16px rgba(0,0,0,0.5)); }
|
||||
@keyframes fadeout { from { opacity: 0.75; transform: scale(1); } to { opacity: 0; transform: scale(1.75); visibility: hidden; } }
|
||||
#tagEditorOverlay { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10000; }
|
||||
#tagEditorModal { background: #1e2132; border: 1px solid #6b7089; border-radius: 8px; padding: 24px; min-width: 640px; max-width: 700px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); }
|
||||
#tagEditorModal h3 { margin-bottom: 16px; color: #c6c8d1; font-size: 14px; font-weight: 600; }
|
||||
.tag-editor-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.tag-editor-fields label { display: flex; flex-direction: column; gap: 2px; margin-bottom: 0; font-size: 11px; color: #6b7089; text-transform: uppercase; letter-spacing: .4px; }
|
||||
#tagEditorModal input, #tagEditorModal select { background: #161821; border: 1px solid #272c42; color: #c6c8d1; padding: 6px 8px; border-radius: 4px; font-size: 12px; outline: none; }
|
||||
#tagEditorModal input:focus, #tagEditorModal select:focus { border-color: #84a0c6; }
|
||||
.tag-editor-buttons { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
|
||||
#timeRemaining { color: #6b7089; }
|
||||
#vuMeter { width: 100%; height: 20px; border-radius: 2px; display: block; }
|
||||
.controls { display: flex; align-items: center; gap: 16px; align-self: flex-end; }
|
||||
|
|
@ -95,6 +103,9 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
|
|||
.track-item .col-time { color: #6b7089; font-size: 11px; font-variant-numeric: tabular-nums; overflow: hidden; white-space: nowrap; }
|
||||
.track-item .col-genre { color: #6b7089; font-size: 10px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
#footerStatus { font-size: 12px; font-weight: 600; white-space: nowrap; position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none; }
|
||||
#footerStatus.success { color: #87be82; }
|
||||
#footerStatus.error { color: #e27878; }
|
||||
#error { display: none; background: rgba(226,120,120,0.15); color: #e27878; padding: 8px 16px; font-size: 13px; text-align: center; border-bottom: 1px solid rgba(226,120,120,0.3); }
|
||||
@media (max-width: 700px) {
|
||||
.player { flex-direction: column; align-items: stretch; }
|
||||
|
|
@ -232,12 +243,36 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
|
|||
<div class="add-bar">
|
||||
<button class="btn btn-primary" id="addFolderBtn">+ Folder</button>
|
||||
<button class="btn" id="addFilesBtn">+ Files</button>
|
||||
<button class="btn" id="infoBtn">Info</button>
|
||||
<button class="btn" id="isolateBtn">Isolate</button>
|
||||
<button class="btn" id="deleteBtn">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tagEditorOverlay">
|
||||
<div id="tagEditorModal">
|
||||
<h3>Edit Tags</h3>
|
||||
<div class="tag-editor-fields">
|
||||
<label>Name <input type="text" id="tagName"></label>
|
||||
<label>Artist <input type="text" id="tagArtist"></label>
|
||||
<label>Album <input type="text" id="tagAlbum"></label>
|
||||
<label>Genre <input type="text" id="tagGenre" list="genreList"></label>
|
||||
<label>Year <input type="text" id="tagYear"></label>
|
||||
<label>Track No. <input type="text" id="tagTrack"></label>
|
||||
<label>Comment <input type="text" id="tagComment"></label>
|
||||
<label>Composer <input type="text" id="tagComposer"></label>
|
||||
<label>URL <input type="text" id="tagUrl"></label>
|
||||
<label>Encoded By <input type="text" id="tagEncodedBy"></label>
|
||||
</div>
|
||||
<datalist id="genreList"></datalist>
|
||||
<div class="tag-editor-buttons">
|
||||
<button class="btn btn-primary" id="tagSaveBtn">Save</button>
|
||||
<button class="btn" id="tagCancelBtn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="footer">
|
||||
<span class="footer-track" id="footerTrack">No track</span>
|
||||
<span id="footerStatus"></span>
|
||||
<span id="footerTime">0:00 / 0:00</span>
|
||||
<span class="brand">noiz<span>://</span></span>
|
||||
</footer>
|
||||
|
|
@ -566,6 +601,7 @@ const timeRemaining = document.getElementById('timeRemaining');
|
|||
const progressRow = document.getElementById('progressRow');
|
||||
const footerTrack = document.getElementById('footerTrack');
|
||||
const footerTime = document.getElementById('footerTime');
|
||||
const footerStatus = document.getElementById('footerStatus');
|
||||
const volumeSlider = document.getElementById('volumeSlider');
|
||||
const volDisplay = document.getElementById('volDisplay');
|
||||
const audioInfo = document.getElementById('audioInfo');
|
||||
|
|
@ -574,6 +610,21 @@ const windowSelect = document.getElementById('windowSelect');
|
|||
const decaySelect = document.getElementById('decaySelect');
|
||||
const barDecaySelect = document.getElementById('barDecaySelect');
|
||||
const crossfadeSelect = document.getElementById('crossfadeSelect');
|
||||
const infoBtn = document.getElementById('infoBtn');
|
||||
const tagEditorOverlay = document.getElementById('tagEditorOverlay');
|
||||
const tagName = document.getElementById('tagName');
|
||||
const tagArtist = document.getElementById('tagArtist');
|
||||
const tagAlbum = document.getElementById('tagAlbum');
|
||||
const tagGenre = document.getElementById('tagGenre');
|
||||
const tagYear = document.getElementById('tagYear');
|
||||
const tagTrack = document.getElementById('tagTrack');
|
||||
const tagComment = document.getElementById('tagComment');
|
||||
const tagComposer = document.getElementById('tagComposer');
|
||||
const tagUrl = document.getElementById('tagUrl');
|
||||
const tagEncodedBy = document.getElementById('tagEncodedBy');
|
||||
const tagSaveBtn = document.getElementById('tagSaveBtn');
|
||||
const tagCancelBtn = document.getElementById('tagCancelBtn');
|
||||
const genreList = document.getElementById('genreList');
|
||||
const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']);
|
||||
const GENRES = [
|
||||
'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal',
|
||||
|
|
@ -887,6 +938,10 @@ async function processFiles(files, paths) {
|
|||
genre: tags.genre || '',
|
||||
year: tags.year || '',
|
||||
track: tags.track || '',
|
||||
comment: tags.comment || '',
|
||||
composer: tags.composer || '',
|
||||
urlTag: tags.urlTag || '',
|
||||
encodedBy: tags.encodedBy || '',
|
||||
filename: file.name,
|
||||
url,
|
||||
file,
|
||||
|
|
@ -1006,6 +1061,59 @@ function deleteTrack() {
|
|||
renderTracks();
|
||||
}
|
||||
}
|
||||
function openTagEditor() {
|
||||
let idx = -1;
|
||||
if (selectedIndices.size > 0) {
|
||||
idx = [...selectedIndices][0];
|
||||
} else if (currentIndex >= 0) {
|
||||
idx = currentIndex;
|
||||
}
|
||||
if (idx < 0) { showStatus('No track selected.', 'error'); return; }
|
||||
const t = tracks[idx];
|
||||
tagEditorOverlay.dataset.idx = idx;
|
||||
tagName.value = t.name || '';
|
||||
tagArtist.value = t.artist || '';
|
||||
tagAlbum.value = t.album || '';
|
||||
tagGenre.value = t.genre || '';
|
||||
tagYear.value = t.year || '';
|
||||
tagTrack.value = t.track || '';
|
||||
tagComment.value = t.comment || '';
|
||||
tagComposer.value = t.composer || '';
|
||||
tagUrl.value = t.urlTag || '';
|
||||
tagEncodedBy.value = t.encodedBy || '';
|
||||
tagEditorOverlay.style.display = 'flex';
|
||||
}
|
||||
function showStatus(msg, type) {
|
||||
footerStatus.textContent = msg;
|
||||
footerStatus.className = type;
|
||||
clearTimeout(footerStatus._timer);
|
||||
footerStatus._timer = setTimeout(() => { footerStatus.textContent = ''; footerStatus.className = ''; }, 2000);
|
||||
}
|
||||
function saveTagEditor() {
|
||||
const idx = parseInt(tagEditorOverlay.dataset.idx, 10);
|
||||
if (isNaN(idx) || idx < 0 || idx >= tracks.length) {
|
||||
showStatus('Error saving tags', 'error');
|
||||
return;
|
||||
}
|
||||
const t = tracks[idx];
|
||||
t.name = tagName.value.trim();
|
||||
t.artist = tagArtist.value.trim();
|
||||
t.album = tagAlbum.value.trim();
|
||||
t.genre = tagGenre.value.trim();
|
||||
t.year = tagYear.value.trim();
|
||||
t.track = tagTrack.value.trim();
|
||||
t.comment = tagComment.value.trim();
|
||||
t.composer = tagComposer.value.trim();
|
||||
t.urlTag = tagUrl.value.trim();
|
||||
t.encodedBy = tagEncodedBy.value.trim();
|
||||
if (idx === currentIndex) updateUI();
|
||||
renderTracks();
|
||||
tagEditorOverlay.style.display = 'none';
|
||||
showStatus('Tag information saved.', 'success');
|
||||
}
|
||||
function cancelTagEditor() {
|
||||
tagEditorOverlay.style.display = 'none';
|
||||
}
|
||||
function getSortFn(field) {
|
||||
switch (field) {
|
||||
case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
|
||||
|
|
@ -1300,6 +1408,23 @@ addFolderBtn.addEventListener('click', addFolder);
|
|||
addFilesBtn.addEventListener('click', addFiles);
|
||||
isolateBtn.addEventListener('click', isolateTracks);
|
||||
deleteBtn.addEventListener('click', deleteTrack);
|
||||
infoBtn.addEventListener('click', openTagEditor);
|
||||
tagSaveBtn.addEventListener('click', saveTagEditor);
|
||||
tagCancelBtn.addEventListener('click', cancelTagEditor);
|
||||
tagEditorOverlay.addEventListener('click', (e) => {
|
||||
if (e.target === tagEditorOverlay) cancelTagEditor();
|
||||
});
|
||||
tagName.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagArtist.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagAlbum.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagGenre.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagYear.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagTrack.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagComment.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagComposer.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagUrl.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
tagEncodedBy.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
|
||||
GENRES.forEach(g => { const opt = document.createElement('option'); opt.value = g; genreList.appendChild(opt); });
|
||||
sortSelect.addEventListener('change', () => {
|
||||
if (sortSelect.value === sortField) { sortAsc = !sortAsc; }
|
||||
else { sortField = sortSelect.value; sortAsc = true; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue