From 890d5dc2b79be8456b62f966dbb762a740871701 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 21:02:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(P4C.T4):=20SoundboardSettings=20=E2=80=94?= =?UTF-8?q?=20sync=20hook=20mount=20+=20per-row=20badges=20+=20remote-dele?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mount useSoundboardSync in SoundboardManagerDialog, thread badges map through SoundboardCategoryGroup/SoundboardRow, render a cloud-state glyph badge inline with each row's size/mime metadata, and wrap handleDelete to attempt a best-effort remote delete via deleteRemoteSound before the local deleteSound call. Co-Authored-By: Claude Sonnet 4.6 --- .../components/SoundboardManagerDialog.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/apps/desktop/src/components/SoundboardManagerDialog.tsx b/apps/desktop/src/components/SoundboardManagerDialog.tsx index 533e077..9d6257e 100644 --- a/apps/desktop/src/components/SoundboardManagerDialog.tsx +++ b/apps/desktop/src/components/SoundboardManagerDialog.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { deleteSound as deleteRemoteSound } from '@chat-app/shared/chat'; import { getPttSettings } from '../lib/pttSettings'; import { codeToShortcut } from '../lib/globalShortcut'; import { invalidate as invalidateSoundCache, preload } from '../lib/soundboardPlayback'; @@ -15,6 +16,8 @@ import { subscribeSoundboardChanges, updateSound, } from '../lib/soundboardStorage'; +import { supabase } from '../lib/supabase'; +import { useSoundboardSync, type SyncBadge } from '../hooks/useSoundboardSync'; import { Modal } from './Modal'; import { AlertIcon, @@ -46,6 +49,7 @@ export function SoundboardManagerDialog({ open, onClose }: Props) { const [busyId, setBusyId] = useState(null); const [error, setError] = useState(null); const [previewingId, setPreviewingId] = useState(null); + const { badges } = useSoundboardSync(); const refresh = useCallback(async () => { setLoading(true); @@ -167,6 +171,11 @@ export function SoundboardManagerDialog({ open, onClose }: Props) { } setBusyId(id); try { + try { + await deleteRemoteSound(supabase, id); + } catch (err) { + console.warn('remote sound delete failed (local delete proceeds)', err); + } await deleteSound(id); invalidateSoundCache(id); if (previewingId === id) stopPreview(); @@ -301,6 +310,7 @@ export function SoundboardManagerDialog({ open, onClose }: Props) { entriesTotal={entries} busyId={busyId} previewingId={previewingId} + badges={badges} onPatch={handlePatch} onDelete={handleDelete} onPreview={handlePreview} @@ -323,6 +333,7 @@ interface GroupProps { entriesTotal: SoundboardEntry[]; busyId: string | null; previewingId: string | null; + badges: Map; onPatch: (id: string, patch: Parameters[1]) => Promise; onDelete: (id: string) => Promise; onPreview: (entry: SoundboardEntry) => Promise; @@ -335,6 +346,7 @@ function SoundboardCategoryGroup({ entriesTotal, busyId, previewingId, + badges, onPatch, onDelete, onPreview, @@ -370,6 +382,7 @@ function SoundboardCategoryGroup({ isLast={idx === entries.length - 1} busy={busyId === entry.id} previewing={previewingId === entry.id} + badge={badges.get(entry.id)} onPatch={onPatch} onDelete={onDelete} onPreview={onPreview} @@ -392,6 +405,7 @@ interface RowProps { isLast: boolean; busy: boolean; previewing: boolean; + badge: SyncBadge | undefined; onPatch: (id: string, patch: Parameters[1]) => Promise; onDelete: (id: string) => Promise; onPreview: (entry: SoundboardEntry) => Promise; @@ -406,6 +420,7 @@ function SoundboardRow({ isLast, busy, previewing, + badge, onPatch, onDelete, onPreview, @@ -503,6 +518,13 @@ function SoundboardRow({ )}

{(entry.size / BYTES_PER_MB).toFixed(2)} MB · {entry.mime} + + {badgeGlyph(badge)} +

@@ -623,3 +645,25 @@ function SoundboardRow({ ); } + +// --------------------------------------------------------------------------- + +function badgeGlyph(b: SyncBadge | undefined): string { + switch (b) { + case 'uploading': return '↑'; + case 'downloading': return '↓'; + case 'error': return '⚠'; + case 'synced': + default: return '☁'; + } +} + +function badgeTitle(b: SyncBadge | undefined): string { + switch (b) { + case 'uploading': return 'Hochladen…'; + case 'downloading': return 'Wird heruntergeladen…'; + case 'error': return 'Synchronisationsfehler'; + case 'synced': + default: return 'Synchronisiert'; + } +}