feat(frontend): composables ze stanem panelu

Logika, która w app.js żyła w zmiennych globalnych, trafia do czterech
composables o wyraźnym zakresie odpowiedzialności:

- useBotStatus     jedno połączenie WebSocket na aplikację, z licznikiem
                   subskrybentów i ponawianiem po zerwaniu. Flaga paused
                   wstrzymuje aktualizacje na czas przeciągania utworów.
- useToasts        kolejka powiadomień wraz z helperem run(), który
                   opakowuje wywołanie API w parę komunikatów sukces/błąd.
- useTrackProgress płynny pasek postępu - między pakietami z serwera czas
                   dopisujemy lokalnie, a każdy pakiet resynchronizuje licznik.
- useUploads       serwerowa baza plików, współdzielona przez kafelek na
                   pulpicie i okno z pełną listą.

Dochodzi też utils/plural.ts z polską odmianą liczebników, zamiast
powtarzanego w kilku miejscach warunku count < 5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
lgooseandClaude Bot committed 2026-08-21 01:08:04 +02:00
1 parent 6bfa9b2a77
commit 119bff00a3
5 files changed
+227

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
import { onUnmounted, ref } from 'vue'
import type { BotStatus } from '@/types/status'
import { useToasts } from '@/composables/useToasts'
const RECONNECT_DELAY_MS = 3000
const status = ref<BotStatus | null>(null)
const connected = ref(false)
/** Wstrzymuje przyjmowanie aktualizacji, gdy użytkownik przeciąga utwory. */
const paused = ref(false)
let socket: WebSocket | null = null
let reconnectTimer: number | null = null
let connectedOnce = false
let subscribers = 0
/** Jedno połączenie WebSocket na aplikację, niezależnie od liczby komponentów. */
export function useBotStatus() {
const { notify } = useToasts()
function open() {
const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
socket = new WebSocket(`${scheme}//${window.location.host}/ws`)
socket.onopen = () => {
connected.value = true
notify(
connectedOnce ? 'Połączenie WebSocket przywrócone (Live)!' : 'WebSocket zsynchronizowany (Live)!',
'success',
)
connectedOnce = true
}
socket.onmessage = (event) => {
if (paused.value) return
try {
status.value = JSON.parse(event.data) as BotStatus
} catch (error) {
console.error('Błąd parsowania danych z WS:', error)
}
}
socket.onclose = () => {
connected.value = false
socket = null
if (subscribers > 0) {
reconnectTimer = window.setTimeout(open, RECONNECT_DELAY_MS)
}
}
socket.onerror = () => socket?.close()
}
function connect() {
subscribers += 1
if (!socket) open()
}
function disconnect() {
subscribers = Math.max(0, subscribers - 1)
if (subscribers > 0) return
if (reconnectTimer !== null) {
window.clearTimeout(reconnectTimer)
reconnectTimer = null
}
socket?.close()
socket = null
}
onUnmounted(disconnect)
return { status, connected, paused, connect, disconnect }
}