From 8df13c5571ea4380fea46f7f46ca3984f79ef795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Marcin=20Brzuchalski?= Date: Tue, 15 Sep 2026 08:06:08 +0200 Subject: [PATCH] fix: stop broadcast failing without a message The zone select started on "None", so a broadcast sent without touching it went nowhere and still showed "Broadcast sent!". The select now starts on the first zone. If no zone is chosen or the sound queue is empty, the page says what is missing instead of showing success. broadcast() now says why it did nothing and logs it. Co-Authored-By: Claude Opus 5 --- app/lib/broadcast.server.ts | 25 +++++++++++++++---- app/locales/en.ts | 9 ++++++- app/locales/pl.ts | 8 +++++- app/routes/broadcast.finish.tsx | 43 ++++++++++++++++++++++++++++----- app/routes/broadcast.zone.tsx | 27 +++++++++++++++++++-- 5 files changed, 97 insertions(+), 15 deletions(-) diff --git a/app/lib/broadcast.server.ts b/app/lib/broadcast.server.ts index 7401813..c50f3fb 100644 --- a/app/lib/broadcast.server.ts +++ b/app/lib/broadcast.server.ts @@ -3,13 +3,20 @@ import {asyncForEach} from '@arcath/utils' import {getPrisma} from './prisma.server' import {addJob} from './queues.server' +export type BroadcastResult = + | {sent: true} + | {sent: false; reason: 'emptyQueue' | 'noPlayableSounds'} + /** * * @param zone The ID of the Zone to broadcase to * @param sounds An array as a JSON string of IDS to play. - * @returns + * @returns Whether the broadcast was queued and, if not, the reason why. */ -export const broadcast = async (zone: string, sounds: string) => { +export const broadcast = async ( + zone: string, + sounds: string +): Promise => { const prisma = getPrisma() const z = await prisma.zone.findFirstOrThrow({ @@ -27,7 +34,9 @@ export const broadcast = async (zone: string, sounds: string) => { } if (soundQueue.length === 0) { - return + console.warn(`Broadcast to zone ${zone} skipped, the sound queue is empty.`) + + return {sent: false, reason: 'emptyQueue'} } const audio = await prisma.audio.findMany({ @@ -43,14 +52,20 @@ export const broadcast = async (zone: string, sounds: string) => { }) if (filteredQueue.length === 0) { - return + console.warn( + `Broadcast to zone ${zone} skipped, none of the queued sounds have a file.` + ) + + return {sent: false, reason: 'noPlayableSounds'} } - return asyncForEach(z.sounders, async ({sounder}) => { + await asyncForEach(z.sounders, async ({sounder}) => { await addJob('broadcast', { ip: sounder.ip, key: sounder.key, sounds: JSON.stringify(filteredQueue) }) }) + + return {sent: true} } diff --git a/app/locales/en.ts b/app/locales/en.ts index 527911f..7594003 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -94,10 +94,17 @@ export const en = { 'broadcast.zone.pageTitle': 'Broadcast (Zone)', 'broadcast.zone.field.zone.label': 'Zone', 'broadcast.zone.field.zone.helper': 'The zone to broadcast the sound to.', - 'broadcast.zone.noneOption': 'None', + 'broadcast.zone.noZones': + 'There are no zones yet. Add a zone before broadcasting.', 'broadcast.zone.submit': 'Broadcast!', 'broadcast.finish.metaTitle': 'Finished', 'broadcast.finish.message': 'Broadcast sent!', + 'broadcast.finish.error.noZone': + 'Nothing was sent. Pick a zone to broadcast to.', + 'broadcast.finish.error.emptyQueue': + 'Nothing was sent. Add at least one sound to the queue.', + 'broadcast.finish.error.noPlayableSounds': + 'Nothing was sent. None of the selected sounds have a file to play.', 'broadcast.finish.startAgain': 'Start again', 'broadcast.finish.rebroadcast': 'Re-broadcast', 'broadcast.tts.metaTitle': 'Text to Speech', diff --git a/app/locales/pl.ts b/app/locales/pl.ts index 0d2e795..4833039 100644 --- a/app/locales/pl.ts +++ b/app/locales/pl.ts @@ -83,10 +83,16 @@ export const pl = { 'broadcast.zone.field.zone.label': 'Strefa', 'broadcast.zone.field.zone.helper': 'Strefa urządzeń, do której ma zostać nadany dźwięk.', - 'broadcast.zone.noneOption': 'Brak', + 'broadcast.zone.noZones': + 'Nie ma jeszcze żadnej strefy. Dodaj strefę przed nadawaniem.', 'broadcast.zone.submit': 'Nadaj!', 'broadcast.finish.metaTitle': 'Zakończono', 'broadcast.finish.message': 'Transmisja wysłana!', + 'broadcast.finish.error.noZone': 'Nic nie wysłano. Wybierz strefę nadawania.', + 'broadcast.finish.error.emptyQueue': + 'Nic nie wysłano. Dodaj do kolejki co najmniej jeden dźwięk.', + 'broadcast.finish.error.noPlayableSounds': + 'Nic nie wysłano. Żaden z wybranych dźwięków nie ma pliku do odtworzenia.', 'broadcast.finish.startAgain': 'Rozpocznij ponownie', 'broadcast.finish.rebroadcast': 'Nadaj ponownie', 'broadcast.tts.metaTitle': 'Tekst na mowę', diff --git a/app/routes/broadcast.finish.tsx b/app/routes/broadcast.finish.tsx index e3b4d12..03de956 100644 --- a/app/routes/broadcast.finish.tsx +++ b/app/routes/broadcast.finish.tsx @@ -13,6 +13,7 @@ import {pageTitle} from '~/lib/utils' import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' +import {initTranslations} from '~/lib/i18n.server' export const meta: MetaFunction = ({matches}) => { const {messages} = getRootI18n(matches) @@ -33,6 +34,8 @@ export const action = async ({request}: ActionFunctionArgs) => { return redirect('/login') } + const {messages} = initTranslations(request) + const formData = await request.formData() const queue = formData.get('queue') as string | undefined @@ -43,8 +46,27 @@ export const action = async ({request}: ActionFunctionArgs) => { invariant(zone) //invariant(desktopGroup) - if (zone !== '_') { - await broadcast(zone, queue) + if (zone === '' || zone === '_') { + return { + queue, + zone, + error: translate(messages, 'broadcast.finish.error.noZone') + } + } + + const broadcastResult = await broadcast(zone, queue) + + if (!broadcastResult.sent) { + return { + queue, + zone, + error: translate( + messages, + broadcastResult.reason === 'emptyQueue' + ? 'broadcast.finish.error.emptyQueue' + : 'broadcast.finish.error.noPlayableSounds' + ) + } } /*if (desktopGroup !== '_') { const audio = await prisma.audio.findFirstOrThrow({where: {id: sound}}) @@ -61,7 +83,7 @@ export const action = async ({request}: ActionFunctionArgs) => { }) }*/ - return {queue, zone} + return {queue, zone, error: null} } const BroadcastFinish = () => { @@ -73,11 +95,13 @@ const BroadcastFinish = () => { return
{t('common.error')}
} - const {queue, zone} = data + const {queue, zone, error} = data return ( -
{t('broadcast.finish.message')}
+
+ {error ? error : t('broadcast.finish.message')} +
@@ -91,7 +115,14 @@ const BroadcastFinish = () => { navigate('/broadcast') } }, - {label: t('broadcast.finish.rebroadcast'), color: 'bg-green-300'} + ...(error + ? [] + : [ + { + label: t('broadcast.finish.rebroadcast'), + color: 'bg-green-300' + } + ]) ]} />
diff --git a/app/routes/broadcast.zone.tsx b/app/routes/broadcast.zone.tsx index 47871aa..fe9d3be 100644 --- a/app/routes/broadcast.zone.tsx +++ b/app/routes/broadcast.zone.tsx @@ -58,6 +58,26 @@ const BroadcastZone = () => { const {queue, zones, count} = data + if (zones.length === 0) { + return ( + +
{t('broadcast.zone.noZones')}
+ { + e.preventDefault() + navigate('/broadcast') + } + } + ]} + /> +
+ ) + } + return (
@@ -72,8 +92,11 @@ const BroadcastZone = () => { label={t('broadcast.zone.field.zone.label')} helperText={t('broadcast.zone.field.zone.helper')} > - {zones.map(({id, name}) => { return (