feat(P4C.T4): SoundboardSettings — sync hook mount + per-row badges + remote-delete
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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { deleteSound as deleteRemoteSound } from '@chat-app/shared/chat';
|
||||||
import { getPttSettings } from '../lib/pttSettings';
|
import { getPttSettings } from '../lib/pttSettings';
|
||||||
import { codeToShortcut } from '../lib/globalShortcut';
|
import { codeToShortcut } from '../lib/globalShortcut';
|
||||||
import { invalidate as invalidateSoundCache, preload } from '../lib/soundboardPlayback';
|
import { invalidate as invalidateSoundCache, preload } from '../lib/soundboardPlayback';
|
||||||
@@ -15,6 +16,8 @@ import {
|
|||||||
subscribeSoundboardChanges,
|
subscribeSoundboardChanges,
|
||||||
updateSound,
|
updateSound,
|
||||||
} from '../lib/soundboardStorage';
|
} from '../lib/soundboardStorage';
|
||||||
|
import { supabase } from '../lib/supabase';
|
||||||
|
import { useSoundboardSync, type SyncBadge } from '../hooks/useSoundboardSync';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import {
|
import {
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
@@ -46,6 +49,7 @@ export function SoundboardManagerDialog({ open, onClose }: Props) {
|
|||||||
const [busyId, setBusyId] = useState<string | null>(null);
|
const [busyId, setBusyId] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [previewingId, setPreviewingId] = useState<string | null>(null);
|
const [previewingId, setPreviewingId] = useState<string | null>(null);
|
||||||
|
const { badges } = useSoundboardSync();
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -167,6 +171,11 @@ export function SoundboardManagerDialog({ open, onClose }: Props) {
|
|||||||
}
|
}
|
||||||
setBusyId(id);
|
setBusyId(id);
|
||||||
try {
|
try {
|
||||||
|
try {
|
||||||
|
await deleteRemoteSound(supabase, id);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('remote sound delete failed (local delete proceeds)', err);
|
||||||
|
}
|
||||||
await deleteSound(id);
|
await deleteSound(id);
|
||||||
invalidateSoundCache(id);
|
invalidateSoundCache(id);
|
||||||
if (previewingId === id) stopPreview();
|
if (previewingId === id) stopPreview();
|
||||||
@@ -301,6 +310,7 @@ export function SoundboardManagerDialog({ open, onClose }: Props) {
|
|||||||
entriesTotal={entries}
|
entriesTotal={entries}
|
||||||
busyId={busyId}
|
busyId={busyId}
|
||||||
previewingId={previewingId}
|
previewingId={previewingId}
|
||||||
|
badges={badges}
|
||||||
onPatch={handlePatch}
|
onPatch={handlePatch}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onPreview={handlePreview}
|
onPreview={handlePreview}
|
||||||
@@ -323,6 +333,7 @@ interface GroupProps {
|
|||||||
entriesTotal: SoundboardEntry[];
|
entriesTotal: SoundboardEntry[];
|
||||||
busyId: string | null;
|
busyId: string | null;
|
||||||
previewingId: string | null;
|
previewingId: string | null;
|
||||||
|
badges: Map<string, SyncBadge>;
|
||||||
onPatch: (id: string, patch: Parameters<typeof updateSound>[1]) => Promise<void>;
|
onPatch: (id: string, patch: Parameters<typeof updateSound>[1]) => Promise<void>;
|
||||||
onDelete: (id: string) => Promise<void>;
|
onDelete: (id: string) => Promise<void>;
|
||||||
onPreview: (entry: SoundboardEntry) => Promise<void>;
|
onPreview: (entry: SoundboardEntry) => Promise<void>;
|
||||||
@@ -335,6 +346,7 @@ function SoundboardCategoryGroup({
|
|||||||
entriesTotal,
|
entriesTotal,
|
||||||
busyId,
|
busyId,
|
||||||
previewingId,
|
previewingId,
|
||||||
|
badges,
|
||||||
onPatch,
|
onPatch,
|
||||||
onDelete,
|
onDelete,
|
||||||
onPreview,
|
onPreview,
|
||||||
@@ -370,6 +382,7 @@ function SoundboardCategoryGroup({
|
|||||||
isLast={idx === entries.length - 1}
|
isLast={idx === entries.length - 1}
|
||||||
busy={busyId === entry.id}
|
busy={busyId === entry.id}
|
||||||
previewing={previewingId === entry.id}
|
previewing={previewingId === entry.id}
|
||||||
|
badge={badges.get(entry.id)}
|
||||||
onPatch={onPatch}
|
onPatch={onPatch}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onPreview={onPreview}
|
onPreview={onPreview}
|
||||||
@@ -392,6 +405,7 @@ interface RowProps {
|
|||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
previewing: boolean;
|
previewing: boolean;
|
||||||
|
badge: SyncBadge | undefined;
|
||||||
onPatch: (id: string, patch: Parameters<typeof updateSound>[1]) => Promise<void>;
|
onPatch: (id: string, patch: Parameters<typeof updateSound>[1]) => Promise<void>;
|
||||||
onDelete: (id: string) => Promise<void>;
|
onDelete: (id: string) => Promise<void>;
|
||||||
onPreview: (entry: SoundboardEntry) => Promise<void>;
|
onPreview: (entry: SoundboardEntry) => Promise<void>;
|
||||||
@@ -406,6 +420,7 @@ function SoundboardRow({
|
|||||||
isLast,
|
isLast,
|
||||||
busy,
|
busy,
|
||||||
previewing,
|
previewing,
|
||||||
|
badge,
|
||||||
onPatch,
|
onPatch,
|
||||||
onDelete,
|
onDelete,
|
||||||
onPreview,
|
onPreview,
|
||||||
@@ -503,6 +518,13 @@ function SoundboardRow({
|
|||||||
)}
|
)}
|
||||||
<p className="text-[10px] text-fg-muted">
|
<p className="text-[10px] text-fg-muted">
|
||||||
{(entry.size / BYTES_PER_MB).toFixed(2)} MB · {entry.mime}
|
{(entry.size / BYTES_PER_MB).toFixed(2)} MB · {entry.mime}
|
||||||
|
<span
|
||||||
|
title={badgeTitle(badge)}
|
||||||
|
aria-label={badgeTitle(badge)}
|
||||||
|
className="ml-2 inline-flex items-center text-[10px] font-medium text-fg-muted"
|
||||||
|
>
|
||||||
|
{badgeGlyph(badge)}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -623,3 +645,25 @@ function SoundboardRow({
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user