UI: Volume-Regler in Toolbar verschoben, Spielt-Anzeige persistent
- Volume-Control von Bottom-Bar in Toolbar verschoben (links von Random) - Pill-förmiges Design passend zu Size-Slider - "Spielt" Anzeige bleibt bis Stop oder neuer Sound sichtbar - Anfangsbuchstaben nur beim ersten Sound jeder Gruppe Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5f0b06550e
commit
4661c366fb
2 changed files with 54 additions and 46 deletions
|
|
@ -216,12 +216,12 @@ export default function App() {
|
||||||
try {
|
try {
|
||||||
await playSound(s.name, guildId, channelId, volume, s.relativePath);
|
await playSound(s.name, guildId, channelId, volume, s.relativePath);
|
||||||
setLastPlayed(s.name);
|
setLastPlayed(s.name);
|
||||||
setTimeout(() => setLastPlayed(''), 4000);
|
|
||||||
} catch (e: any) { notify(e?.message || 'Play fehlgeschlagen', 'error'); }
|
} catch (e: any) { notify(e?.message || 'Play fehlgeschlagen', 'error'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleStop() {
|
async function handleStop() {
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
|
setLastPlayed('');
|
||||||
try { await fetch(`/api/stop?guildId=${encodeURIComponent(guildId)}`, { method: 'POST' }); } catch { }
|
try { await fetch(`/api/stop?guildId=${encodeURIComponent(guildId)}`, { method: 'POST' }); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,6 +421,34 @@ export default function App() {
|
||||||
|
|
||||||
<div className="toolbar-spacer" />
|
<div className="toolbar-spacer" />
|
||||||
|
|
||||||
|
<div className="volume-control">
|
||||||
|
<span
|
||||||
|
className="material-icons vol-icon"
|
||||||
|
onClick={() => {
|
||||||
|
const newVol = volume > 0 ? 0 : 0.5;
|
||||||
|
setVolume(newVol);
|
||||||
|
if (guildId) setVolumeLive(guildId, newVol).catch(() => {});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{volume === 0 ? 'volume_off' : volume < 0.5 ? 'volume_down' : 'volume_up'}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
className="vol-slider"
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
value={volume}
|
||||||
|
onChange={async e => {
|
||||||
|
const v = parseFloat(e.target.value);
|
||||||
|
setVolume(v);
|
||||||
|
if (guildId) try { await setVolumeLive(guildId, v); } catch { }
|
||||||
|
}}
|
||||||
|
style={{ '--vol': `${Math.round(volume * 100)}%` } as React.CSSProperties}
|
||||||
|
/>
|
||||||
|
<span className="vol-pct">{Math.round(volume * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button className="tb-btn random" onClick={handleRandom} title="Zufälliger Sound">
|
<button className="tb-btn random" onClick={handleRandom} title="Zufälliger Sound">
|
||||||
<span className="material-icons tb-icon">shuffle</span>
|
<span className="material-icons tb-icon">shuffle</span>
|
||||||
Random
|
Random
|
||||||
|
|
@ -576,33 +604,6 @@ export default function App() {
|
||||||
<span className="np-label">Spielt:</span>
|
<span className="np-label">Spielt:</span>
|
||||||
<span className="np-name">{lastPlayed || '—'}</span>
|
<span className="np-name">{lastPlayed || '—'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="volume-section">
|
|
||||||
<span
|
|
||||||
className="material-icons volume-icon"
|
|
||||||
onClick={() => {
|
|
||||||
const newVol = volume > 0 ? 0 : 0.5;
|
|
||||||
setVolume(newVol);
|
|
||||||
if (guildId) setVolumeLive(guildId, newVol).catch(() => {});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{volume === 0 ? 'volume_off' : volume < 0.5 ? 'volume_down' : 'volume_up'}
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
className="volume-slider"
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
step={0.01}
|
|
||||||
value={volume}
|
|
||||||
onChange={async e => {
|
|
||||||
const v = parseFloat(e.target.value);
|
|
||||||
setVolume(v);
|
|
||||||
if (guildId) try { await setVolumeLive(guildId, v); } catch { }
|
|
||||||
}}
|
|
||||||
style={{ '--vol': `${Math.round(volume * 100)}%` } as React.CSSProperties}
|
|
||||||
/>
|
|
||||||
<span className="volume-pct">{Math.round(volume * 100)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ═══ CONTEXT MENU ═══ */}
|
{/* ═══ CONTEXT MENU ═══ */}
|
||||||
|
|
|
||||||
|
|
@ -1025,57 +1025,64 @@ input, select {
|
||||||
.np-wave-bar:nth-child(3) { height: 6px; animation-delay: 240ms; }
|
.np-wave-bar:nth-child(3) { height: 6px; animation-delay: 240ms; }
|
||||||
.np-wave-bar:nth-child(4) { height: 11px; animation-delay: 80ms; }
|
.np-wave-bar:nth-child(4) { height: 11px; animation-delay: 80ms; }
|
||||||
|
|
||||||
/* ── Volume ── */
|
/* ── Volume Control (Toolbar) ── */
|
||||||
.volume-section {
|
.volume-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
margin-left: auto;
|
padding: 4px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-icon {
|
.vol-icon {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
color: var(--text-muted);
|
color: var(--text-faint);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color var(--transition);
|
transition: color var(--transition);
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-icon:hover {
|
.vol-icon:hover {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-slider {
|
.vol-slider {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 90px;
|
width: 80px;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: linear-gradient(to right, var(--accent) 0%, var(--accent) var(--vol, 80%), var(--bg-tertiary) var(--vol, 80%));
|
background: linear-gradient(to right, var(--accent) 0%, var(--accent) var(--vol, 80%), var(--bg-modifier-selected) var(--vol, 80%));
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-slider::-webkit-slider-thumb {
|
.vol-slider::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--white);
|
background: var(--accent);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-slider::-moz-range-thumb {
|
.vol-slider::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vol-slider::-moz-range-thumb {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--white);
|
background: var(--accent);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
|
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-pct {
|
.vol-pct {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-faint);
|
color: var(--text-faint);
|
||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue