feat: połączenie przycisków zapętlania w jeden przycisk z cyklem Wyłączone -> Kolejka -> Utwór (styl YT Music/Spotify)

This commit is contained in:
mmacierzanka committed 2026-07-04 16:41:17 +02:00
1 parent 2a399364d8
commit ab6f16b57d
4 files changed
+86 -59

No files matched your search

+18 -10
View File
@@ -599,19 +599,27 @@ class MusicBot(commands.Cog):
ephemeral=True ephemeral=True
) )
@app_commands.command(name="loop", description="Włącz/wyłącz zapętlanie aktualnego utworu") @app_commands.command(name="loop", description="Zmień tryb zapętlania (Wyłączone -> Cała kolejka -> Jeden utwór)")
async def loop_command(self, interaction: discord.Interaction): async def loop_command(self, interaction: discord.Interaction):
"""Włącz/wyłącz zapętlanie aktualnego utworu""" """Zmień tryb zapętlania jak w YT Music/Spotify"""
guild_id = interaction.guild.id guild_id = interaction.guild.id
loop_current = not self.get_loop_current(guild_id) loop_current = self.get_loop_current(guild_id)
self.set_loop_current(guild_id, loop_current) loop_pl = self.get_loop_playlist(guild_id)
if loop_current:
if not loop_current and not loop_pl:
self.set_loop_playlist(guild_id, True)
self.set_loop_current(guild_id, False)
msg = "🔁 Tryb zapętlania: **Cała kolejka (playlista)**"
elif loop_pl and not loop_current:
self.set_loop_playlist(guild_id, False) self.set_loop_playlist(guild_id, False)
state = "włączone" if loop_current else "wyłączone" self.set_loop_current(guild_id, True)
await interaction.response.send_message( msg = "🔂 Tryb zapętlania: **Jeden utwór**"
f"Zapętlanie aktualnego utworu zostało {state}.", else:
ephemeral=False self.set_loop_playlist(guild_id, False)
) self.set_loop_current(guild_id, False)
msg = "➡️ Tryb zapętlania: **Wyłączone**"
await interaction.response.send_message(msg, ephemeral=False)
@app_commands.command(name="loopplaylist", description="Włącz/wyłącz zapętlanie całej kolejki (tryb playlisty)") @app_commands.command(name="loopplaylist", description="Włącz/wyłącz zapętlanie całej kolejki (tryb playlisty)")
async def loopplaylist_command(self, interaction: discord.Interaction): async def loopplaylist_command(self, interaction: discord.Interaction):
+38 -45
View File
@@ -31,7 +31,6 @@ const playPauseIcon = document.getElementById('play-pause-icon');
const btnSkip = document.getElementById('btn-skip'); const btnSkip = document.getElementById('btn-skip');
const btnStop = document.getElementById('btn-stop'); const btnStop = document.getElementById('btn-stop');
const btnLoop = document.getElementById('btn-loop'); const btnLoop = document.getElementById('btn-loop');
const btnLoopPlaylist = document.getElementById('btn-loop-playlist');
const vinylRecord = document.getElementById('vinyl-record'); const vinylRecord = document.getElementById('vinyl-record');
const musicWave = document.getElementById('music-wave'); const musicWave = document.getElementById('music-wave');
const volumeSlider = document.getElementById('volume-slider'); const volumeSlider = document.getElementById('volume-slider');
@@ -142,9 +141,6 @@ function bindEvents() {
btnSkip.addEventListener('click', skipTrack); btnSkip.addEventListener('click', skipTrack);
btnStop.addEventListener('click', stopPlayback); btnStop.addEventListener('click', stopPlayback);
btnLoop.addEventListener('click', toggleLoop); btnLoop.addEventListener('click', toggleLoop);
if (btnLoopPlaylist) {
btnLoopPlaylist.addEventListener('click', toggleLoopPlaylist);
}
// Volume slider // Volume slider
volumeSlider.addEventListener('input', (e) => { volumeSlider.addEventListener('input', (e) => {
@@ -375,28 +371,39 @@ function updateUI(status) {
updateVolumeIcon(status.volume); updateVolumeIcon(status.volume);
} }
// 5. Loop State // 5. Loop State (Combined toggle: Off -> Playlist -> Single)
if (status.loop_single) { let loopMode = 'off';
loopBadge.style.display = 'flex'; if (status.loop_single) loopMode = 'single';
btnLoop.classList.add('active'); else if (status.loop_playlist) loopMode = 'playlist';
btnLoop.style.background = 'var(--primary)';
btnLoop.style.color = 'var(--bg-card)';
} else {
loopBadge.style.display = 'none';
btnLoop.classList.remove('active');
btnLoop.style.background = '';
btnLoop.style.color = '';
}
if (btnLoopPlaylist) { if (btnLoop.dataset.loopMode !== loopMode) {
if (status.loop_playlist) { btnLoop.dataset.loopMode = loopMode;
btnLoopPlaylist.classList.add('active'); if (loopMode === 'single') {
btnLoopPlaylist.style.background = 'var(--primary)'; btnLoop.innerHTML = '<i data-lucide="repeat-1"></i>';
btnLoopPlaylist.style.color = 'var(--bg-card)'; btnLoop.classList.add('active');
btnLoop.style.background = 'var(--primary)';
btnLoop.style.color = 'var(--bg-card)';
btnLoop.title = "Zapętlenie: jeden utwór (kliknij, aby wyłączyć)";
loopBadge.style.display = 'flex';
loopBadge.innerHTML = '<i data-lucide="repeat-1"></i> Zapętlenie utworu';
} else if (loopMode === 'playlist') {
btnLoop.innerHTML = '<i data-lucide="repeat"></i>';
btnLoop.classList.add('active');
btnLoop.style.background = 'var(--primary)';
btnLoop.style.color = 'var(--bg-card)';
btnLoop.title = "Zapętlenie: cała kolejka (kliknij, aby zapętlić jeden utwór)";
loopBadge.style.display = 'flex';
loopBadge.innerHTML = '<i data-lucide="repeat"></i> Zapętlenie kolejki';
} else { } else {
btnLoopPlaylist.classList.remove('active'); btnLoop.innerHTML = '<i data-lucide="repeat"></i>';
btnLoopPlaylist.style.background = ''; btnLoop.classList.remove('active');
btnLoopPlaylist.style.color = ''; btnLoop.style.background = '';
btnLoop.style.color = '';
btnLoop.title = "Zapętlenie: wyłączone (kliknij, aby zapętlić kolejkę)";
loopBadge.style.display = 'none';
}
if (window.lucide) {
lucide.createIcons();
} }
} }
@@ -700,35 +707,21 @@ async function stopPlayback() {
} }
} }
// Toggle Loop State // Toggle Loop State (Cycle: Off -> Playlist -> Single -> Off)
async function toggleLoop() { async function toggleLoop() {
if (!currentStatus) return; if (!currentStatus) return;
const nextState = !currentStatus.loop_single;
try { try {
const res = await fetch('/api/loop', { const res = await fetch('/api/loop_cycle', { method: 'POST' });
method: 'POST', if (!res.ok) throw new Error('Loop cycle failed');
headers: { 'Content-Type': 'application/json' }, const data = await res.json();
body: JSON.stringify({ loop: nextState }) if (data.mode === 'playlist') showToast('Zapętlanie kolejki włączone', 'info');
}); else if (data.mode === 'single') showToast('Zapętlanie utworu włączone', 'info');
if (!res.ok) throw new Error('Loop failed'); else showToast('Zapętlanie wyłączone', 'info');
showToast(nextState ? 'Zapętlanie utworu włączone' : 'Zapętlanie utworu wyłączone', 'info');
} catch (err) { } catch (err) {
showToast('Błąd ustawiania pętli', 'error'); showToast('Błąd ustawiania pętli', 'error');
} }
} }
// Toggle Playlist Loop State
async function toggleLoopPlaylist() {
try {
const res = await fetch('/api/loop_playlist', { method: 'POST' });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Błąd zapętlania');
showToast(data.loop_playlist ? 'Włączono zapętlanie całej kolejki (playlisty)!' : 'Wyłączono zapętlanie kolejki.', 'info');
} catch (err) {
showToast('Nie udało się zmienić trybu zapętlania', 'error');
}
}
// Set Volume // Set Volume
async function setVolume(vol) { async function setVolume(vol) {
try { try {
+1 -4
View File
@@ -112,10 +112,7 @@
<button class="btn-icon btn-secondary" id="btn-skip" title="Pomiń utwór"> <button class="btn-icon btn-secondary" id="btn-skip" title="Pomiń utwór">
<i data-lucide="skip-forward"></i> <i data-lucide="skip-forward"></i>
</button> </button>
<button class="btn-icon btn-secondary" id="btn-loop" title="Zapętl ten utwór"> <button class="btn-icon btn-secondary" id="btn-loop" title="Zapętlanie: wyłączone">
<i data-lucide="repeat-1"></i>
</button>
<button class="btn-icon btn-secondary" id="btn-loop-playlist" title="Zapętlij całą kolejkę (tryb playlisty)">
<i data-lucide="repeat"></i> <i data-lucide="repeat"></i>
</button> </button>
</div> </div>
+29
View File
@@ -402,6 +402,35 @@ async def api_loop_playlist():
await broadcast_status() await broadcast_status()
return {"status": "success", "loop_playlist": new_state} return {"status": "success", "loop_playlist": new_state}
@app.post("/api/loop_cycle")
async def api_loop_cycle():
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
guild = get_active_guild()
if not guild:
raise HTTPException(status_code=400, detail="Bot has no active server context")
loop_current = music_cog.get_loop_current(guild.id)
loop_pl = music_cog.get_loop_playlist(guild.id)
if not loop_current and not loop_pl:
music_cog.set_loop_playlist(guild.id, True)
music_cog.set_loop_current(guild.id, False)
mode = "playlist"
elif loop_pl and not loop_current:
music_cog.set_loop_playlist(guild.id, False)
music_cog.set_loop_current(guild.id, True)
mode = "single"
else:
music_cog.set_loop_playlist(guild.id, False)
music_cog.set_loop_current(guild.id, False)
mode = "off"
await broadcast_status()
return {"status": "success", "mode": mode}
@app.post("/api/remove") @app.post("/api/remove")
async def api_remove(req: RemoveRequest): async def api_remove(req: RemoveRequest):
music_cog = bot.get_cog("MusicBot") music_cog = bot.get_cog("MusicBot")