feat: show optimistic history queue entries

This commit is contained in:
mmacierzanka committed 2026-08-21 21:45:25 +02:00
1 parent 473e40f25e
commit 07041ac881
4 files changed
+89 -14

No files matched your search

@@ -2,12 +2,14 @@
import { ref } from 'vue'
import { Plus, PlusCircle, Search } from 'lucide-vue-next'
import { api } from '@/api/client'
import { useBotStatus } from '@/composables/useBotStatus'
import { useToasts } from '@/composables/useToasts'
import type { BotStatus } from '@/types/status'
const props = defineProps<{ status: BotStatus | null }>()
const { notify, run } = useToasts()
const { addPendingTrack, removePendingTrack } = useBotStatus()
const query = ref('')
const busy = ref(false)
@@ -21,7 +23,10 @@ async function submit() {
}
busy.value = true
const guildId = props.status.active_voice_channel.guild_id
const pendingId = addPendingTrack(guildId, trimmed)
const added = await run(() => api.play(trimmed), 'Dodano utwór do kolejki!', 'Błąd dodawania')
removePendingTrack(pendingId)
if (added) query.value = ''
busy.value = false
}
+16 -9
View File
@@ -2,6 +2,7 @@
import { computed } from 'vue'
import { Clock, History, Plus, RotateCcw } from 'lucide-vue-next'
import { api } from '@/api/client'
import { useBotStatus } from '@/composables/useBotStatus'
import { useToasts } from '@/composables/useToasts'
import { useUploads } from '@/composables/useUploads'
import { formatTime } from '@/composables/useTrackProgress'
@@ -11,6 +12,7 @@ import type { BotStatus, HistoryTrack } from '@/types/status'
const props = defineProps<{ status: BotStatus | null }>()
const { notify, run } = useToasts()
const { addPendingTrack, removePendingTrack } = useBotStatus()
const { enqueue } = useUploads()
const history = computed(() => props.status?.history ?? [])
@@ -21,16 +23,21 @@ async function replay(entry: HistoryTrack) {
return
}
if (entry.is_local) {
const path = entry.filepath ?? entry.url ?? entry.title
await enqueue(path.split(/[\\/]/).pop() ?? path)
return
const pendingId = addPendingTrack(props.status.active_voice_channel.guild_id, entry.title)
try {
if (entry.is_local) {
const path = entry.filepath ?? entry.url ?? entry.title
await enqueue(path.split(/[\\/]/).pop() ?? path)
return
}
if (!entry.url) {
notify('Ten wpis historii nie ma źródła do odtworzenia', 'error')
return
}
await run(() => api.play(entry.url as string), 'Dodano z historii do kolejki!', 'Błąd')
} finally {
removePendingTrack(pendingId)
}
if (!entry.url) {
notify('Ten wpis historii nie ma źródła do odtworzenia', 'error')
return
}
await run(() => api.play(entry.url as string), 'Dodano z historii do kolejki!', 'Błąd')
}
</script>
+46 -4
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Disc, ListMusic, RotateCcw } from 'lucide-vue-next'
import { Disc, ListMusic, LoaderCircle, RotateCcw } from 'lucide-vue-next'
import QueueItem from '@/components/QueueItem.vue'
import { api } from '@/api/client'
import { useBotStatus } from '@/composables/useBotStatus'
@@ -11,11 +11,15 @@ import type { BotStatus } from '@/types/status'
const props = defineProps<{ status: BotStatus | null }>()
const { notify, run } = useToasts()
const { paused } = useBotStatus()
const { paused, pendingTracks } = useBotStatus()
const queue = computed(() => props.status?.queue ?? [])
const queueIndex = computed(() => props.status?.queue_index ?? -1)
const savedQueueAvailable = computed(() => props.status?.saved_queue_available ?? false)
const currentGuildId = computed(() => props.status?.active_voice_channel?.guild_id ?? null)
const pendingForGuild = computed(() =>
pendingTracks.value.filter((track) => track.guildId === currentGuildId.value),
)
const draggedId = ref<string | null>(null)
const dropTargetId = ref<string | null>(null)
@@ -89,7 +93,7 @@ const restoreQueue = () =>
<h2 class="card-title">
<ListMusic /> Kolejka odtwarzania
</h2>
<span class="queue-count">{{ tracksLabel(queue.length) }}</span>
<span class="queue-count">{{ tracksLabel(queue.length + pendingForGuild.length) }}</span>
</div>
<div class="queue-list-container">
<ul class="queue-list">
@@ -114,8 +118,25 @@ const restoreQueue = () =>
@set-plays="setPlays"
@invalid-plays="warnInvalidPlays"
/>
<li
v-for="track in pendingForGuild"
:key="track.id"
class="queue-item queue-item-pending"
>
<div class="queue-index">
<LoaderCircle class="loading-icon" />
</div>
<div class="queue-track-info">
<div class="queue-track-title">
{{ track.title }}
</div>
<div class="queue-pending-label">
Ładowanie utworu…
</div>
</div>
</li>
<div
v-if="!queue.length"
v-if="!queue.length && !pendingForGuild.length"
class="empty-queue-message"
>
<Disc />
@@ -138,4 +159,25 @@ const restoreQueue = () =>
.restore-queue-button {
margin-top: 14px;
}
.queue-item-pending {
opacity: 0.7;
}
.loading-icon {
color: var(--primary);
animation: spin 1s linear infinite;
}
.queue-pending-label {
margin-top: 6px;
color: var(--primary-hover);
font-size: 0.75rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
+22 -1
View File
@@ -9,6 +9,7 @@ const status = ref<BotStatus | null>(null)
const connected = ref(false)
/** Wstrzymuje przyjmowanie aktualizacji, gdy użytkownik przeciąga utwory. */
const paused = ref(false)
const pendingTracks = ref<{ id: string; guildId: string; title: string }[]>([])
let socket: WebSocket | null = null
let reconnectTimer: number | null = null
@@ -88,7 +89,27 @@ export function useBotStatus() {
}
}
function addPendingTrack(guildId: string, title: string) {
const id = `pending-${Date.now()}-${Math.random().toString(36).slice(2)}`
pendingTracks.value.push({ id, guildId, title })
return id
}
function removePendingTrack(id: string) {
pendingTracks.value = pendingTracks.value.filter((track) => track.id !== id)
}
onUnmounted(disconnect)
return { status, connected, paused, connect, disconnect, selectGuild }
return {
status,
connected,
paused,
pendingTracks,
connect,
disconnect,
selectGuild,
addPendingTrack,
removePendingTrack,
}
}