feat(soundboard): download modal with filename input + fix yt-dlp binary

- Add download modal: filename input, progress phases (input/downloading/done/error)
- Refactor backend: shared handleUrlDownload() with optional custom filename + rename
- Fix Dockerfile: use yt-dlp_linux standalone binary (no Python dependency)
- Modal shows URL type badge (YouTube/Instagram/MP3), spinner, retry on error

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-06 23:59:31 +01:00
parent 0b2ba0ef86
commit 9ff8a38547
4 changed files with 332 additions and 76 deletions

View file

@ -2032,6 +2032,141 @@
margin-top: 2px;
}
/*
Download Modal
*/
.dl-modal-overlay {
position: fixed; inset: 0;
background: rgba(0, 0, 0, .55);
display: flex; align-items: center; justify-content: center;
z-index: 300;
animation: fade-in 150ms ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.dl-modal {
width: 420px; max-width: 92vw;
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, .1);
border-radius: 16px;
box-shadow: 0 12px 60px rgba(0, 0, 0, .5);
animation: scale-in 200ms cubic-bezier(.16, 1, .3, 1);
}
@keyframes scale-in { from { opacity: 0; transform: scale(.95); } to { opacity: 1; transform: scale(1); } }
.dl-modal-header {
display: flex; align-items: center; gap: 8px;
padding: 14px 16px;
border-bottom: 1px solid rgba(255, 255, 255, .06);
font-size: 14px; font-weight: 700;
color: var(--text-normal);
}
.dl-modal-header .material-icons { color: var(--accent); }
.dl-modal-close {
margin-left: auto;
display: flex; align-items: center; justify-content: center;
width: 26px; height: 26px; border-radius: 50%;
border: none; background: rgba(255,255,255,.06);
color: var(--text-muted); cursor: pointer;
transition: background var(--transition);
}
.dl-modal-close:hover { background: rgba(255,255,255,.14); color: var(--text-normal); }
.dl-modal-body { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
/* URL display */
.dl-modal-url {
display: flex; align-items: center; gap: 8px;
padding: 8px 10px; border-radius: 8px;
background: rgba(0, 0, 0, .2);
overflow: hidden;
}
.dl-modal-tag {
flex-shrink: 0; padding: 2px 8px; border-radius: 6px;
font-size: 10px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
}
.dl-modal-tag.youtube { background: rgba(255, 0, 0, .18); color: #ff4444; }
.dl-modal-tag.instagram { background: rgba(225, 48, 108, .18); color: #e1306c; }
.dl-modal-tag.mp3 { background: rgba(46, 204, 113, .18); color: #2ecc71; }
.dl-modal-url-text {
font-size: 11px; color: var(--text-faint);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Filename field */
.dl-modal-field { display: flex; flex-direction: column; gap: 5px; }
.dl-modal-label { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.dl-modal-input-wrap {
display: flex; align-items: center;
border: 1px solid rgba(255, 255, 255, .1); border-radius: 8px;
background: rgba(0, 0, 0, .15);
overflow: hidden;
transition: border-color var(--transition);
}
.dl-modal-input-wrap:focus-within { border-color: var(--accent); }
.dl-modal-input {
flex: 1; border: none; background: transparent;
padding: 8px 10px; color: var(--text-normal);
font-size: 13px; font-family: var(--font); outline: none;
}
.dl-modal-input::placeholder { color: var(--text-faint); }
.dl-modal-ext {
padding: 0 10px; font-size: 12px; font-weight: 600;
color: var(--text-faint); background: rgba(255, 255, 255, .04);
align-self: stretch; display: flex; align-items: center;
}
.dl-modal-hint { font-size: 10px; color: var(--text-faint); }
/* Progress spinner */
.dl-modal-progress {
display: flex; align-items: center; gap: 12px;
padding: 20px 0; justify-content: center;
font-size: 13px; color: var(--text-muted);
}
.dl-modal-spinner {
width: 24px; height: 24px; border-radius: 50%;
border: 3px solid rgba(var(--accent-rgb), .2);
border-top-color: var(--accent);
animation: spin 800ms linear infinite;
}
/* Success */
.dl-modal-success {
display: flex; align-items: center; gap: 10px;
padding: 16px 0; justify-content: center;
font-size: 13px; color: var(--text-normal);
}
.dl-modal-check { color: #2ecc71; font-size: 28px; }
/* Error */
.dl-modal-error {
display: flex; align-items: center; gap: 10px;
padding: 12px 0; justify-content: center;
font-size: 13px; color: #e74c3c;
}
/* Actions */
.dl-modal-actions {
display: flex; justify-content: flex-end; gap: 8px;
padding: 0 16px 14px;
}
.dl-modal-cancel {
padding: 7px 14px; border-radius: 8px;
border: 1px solid rgba(255, 255, 255, .1); background: transparent;
color: var(--text-muted); font-size: 12px; font-weight: 600;
cursor: pointer; transition: all var(--transition);
}
.dl-modal-cancel:hover { background: rgba(255,255,255,.06); color: var(--text-normal); }
.dl-modal-submit {
display: flex; align-items: center; gap: 5px;
padding: 7px 16px; border-radius: 8px;
border: none; background: var(--accent);
color: #fff; font-size: 12px; font-weight: 700;
cursor: pointer; transition: filter var(--transition);
}
.dl-modal-submit:hover { filter: brightness(1.15); }
/*
Utility
*/