revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import { Layers, Loader2, Music, Pencil, Plus, Search, Trash2, Upload } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { deleteSoundtrack, updateSoundtrack } from "@/actions/soundtracks";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Layers, Loader2, Music, Pencil, Plus, Search, Trash2, Upload } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { deleteSoundtrack, updateSoundtrack } from '@/actions/soundtracks'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -14,40 +14,53 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useServerAction } from "@/hooks/use-server-action";
|
||||
import { invalidateSongPickerCache } from "@/lib/client-cache/song-picker-cache";
|
||||
import { parseTraxChannels } from "@/lib/trax-format";
|
||||
import { TraxPlayer } from "./trax-player";
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useServerAction } from '@/hooks/use-server-action'
|
||||
import { invalidateSongPickerCache } from '@/lib/client-cache/song-picker-cache'
|
||||
import { parseTraxChannels } from '@/lib/trax-format'
|
||||
import { TraxPlayer } from './trax-player'
|
||||
|
||||
interface SoundtrackRow {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
author: string;
|
||||
length: number;
|
||||
track: string;
|
||||
samples: number[];
|
||||
kind: "single" | "composition";
|
||||
usedBy: number;
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
author: string
|
||||
length: number
|
||||
track: string
|
||||
samples: number[]
|
||||
kind: 'single' | 'composition'
|
||||
usedBy: number
|
||||
}
|
||||
|
||||
function formatLength(seconds: number): string {
|
||||
if (!seconds || seconds <= 0) return "—";
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
return `${m}:${String(s).padStart(2, "0")}`;
|
||||
if (!seconds || seconds <= 0) return '—'
|
||||
const m = Math.floor(seconds / 60)
|
||||
const s = seconds % 60
|
||||
return `${m}:${String(s).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function formatSamples(samples: number[]): string {
|
||||
if (samples.length === 0) return "—";
|
||||
if (samples.length <= 20) return samples.join(", ");
|
||||
return `${samples.slice(0, 20).join(", ")}, …(+${samples.length - 20})`;
|
||||
if (samples.length === 0) return '—'
|
||||
if (samples.length <= 20) return samples.join(', ')
|
||||
return `${samples.slice(0, 20).join(', ')}, …(+${samples.length - 20})`
|
||||
}
|
||||
|
||||
export function SoundsClient({
|
||||
@@ -55,56 +68,56 @@ export function SoundsClient({
|
||||
canEdit,
|
||||
baseUrl,
|
||||
}: {
|
||||
soundtracks: SoundtrackRow[];
|
||||
canEdit: boolean;
|
||||
baseUrl: string;
|
||||
soundtracks: SoundtrackRow[]
|
||||
canEdit: boolean
|
||||
baseUrl: string
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { run, isPending } = useServerAction();
|
||||
const router = useRouter()
|
||||
const { run, isPending } = useServerAction()
|
||||
|
||||
const [query, setQuery] = useState("");
|
||||
const [kindFilter, setKindFilter] = useState<"all" | "single" | "composition">("all");
|
||||
const [uploadOpen, setUploadOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<SoundtrackRow | null>(null);
|
||||
const [deleting, setDeleting] = useState<SoundtrackRow | null>(null);
|
||||
const [playingId, setPlayingId] = useState<number | null>(null);
|
||||
const [query, setQuery] = useState('')
|
||||
const [kindFilter, setKindFilter] = useState<'all' | 'single' | 'composition'>('all')
|
||||
const [uploadOpen, setUploadOpen] = useState(false)
|
||||
const [editing, setEditing] = useState<SoundtrackRow | null>(null)
|
||||
const [deleting, setDeleting] = useState<SoundtrackRow | null>(null)
|
||||
const [playingId, setPlayingId] = useState<number | null>(null)
|
||||
|
||||
const counts = useMemo(() => {
|
||||
let single = 0;
|
||||
let composition = 0;
|
||||
let single = 0
|
||||
let composition = 0
|
||||
for (const s of soundtracks) {
|
||||
if (s.kind === "composition") composition++;
|
||||
else single++;
|
||||
if (s.kind === 'composition') composition++
|
||||
else single++
|
||||
}
|
||||
return { single, composition };
|
||||
}, [soundtracks]);
|
||||
return { single, composition }
|
||||
}, [soundtracks])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = query.trim().toLowerCase();
|
||||
const q = query.trim().toLowerCase()
|
||||
return soundtracks.filter((s) => {
|
||||
if (kindFilter !== "all" && s.kind !== kindFilter) return false;
|
||||
if (!q) return true;
|
||||
if (kindFilter !== 'all' && s.kind !== kindFilter) return false
|
||||
if (!q) return true
|
||||
return (
|
||||
s.name.toLowerCase().includes(q) ||
|
||||
s.author.toLowerCase().includes(q) ||
|
||||
s.code.toLowerCase().includes(q) ||
|
||||
String(s.id).includes(q)
|
||||
);
|
||||
});
|
||||
}, [soundtracks, query, kindFilter]);
|
||||
)
|
||||
})
|
||||
}, [soundtracks, query, kindFilter])
|
||||
|
||||
function handleDelete() {
|
||||
if (!deleting) return;
|
||||
const id = deleting.id;
|
||||
if (!deleting) return
|
||||
const id = deleting.id
|
||||
run(() => deleteSoundtrack({ id }), {
|
||||
successMessage: `Song disk #${id} eliminato.`,
|
||||
errorMessage: `Impossibile eliminare il song disk #${id}.`,
|
||||
onSuccess: () => {
|
||||
invalidateSongPickerCache();
|
||||
setDeleting(null);
|
||||
router.refresh();
|
||||
invalidateSongPickerCache()
|
||||
setDeleting(null)
|
||||
router.refresh()
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -159,12 +172,17 @@ export function SoundsClient({
|
||||
<TableBody>
|
||||
{filtered.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={canEdit ? 9 : 8} className="text-center text-muted-foreground py-8">
|
||||
<TableCell
|
||||
colSpan={canEdit ? 9 : 8}
|
||||
className="text-center text-muted-foreground py-8"
|
||||
>
|
||||
{soundtracks.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Music className="h-8 w-8 opacity-50" />
|
||||
<p>Nessun song disk caricato.</p>
|
||||
{canEdit && <p className="text-xs">Clicca “Upload MP3” per iniziare.</p>}
|
||||
{canEdit && (
|
||||
<p className="text-xs">Clicca “Upload MP3” per iniziare.</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>Nessun risultato per i filtri attivi.</>
|
||||
@@ -176,7 +194,7 @@ export function SoundsClient({
|
||||
<TableRow key={s.id}>
|
||||
<TableCell className="font-mono text-xs">{s.id}</TableCell>
|
||||
<TableCell>
|
||||
{s.kind === "composition" ? (
|
||||
{s.kind === 'composition' ? (
|
||||
<Badge variant="outline" className="gap-1">
|
||||
<Layers className="h-3 w-3" />
|
||||
Trax
|
||||
@@ -190,7 +208,7 @@ export function SoundsClient({
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs break-all">{s.code}</TableCell>
|
||||
<TableCell className="font-medium">{s.name}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{s.author || "—"}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{s.author || '—'}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{formatLength(s.length)}</TableCell>
|
||||
<TableCell>
|
||||
{s.usedBy > 0 ? (
|
||||
@@ -200,7 +218,7 @@ export function SoundsClient({
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{s.kind === "composition" ? (
|
||||
{s.kind === 'composition' ? (
|
||||
<div className="space-y-1 max-w-[300px]">
|
||||
<TraxPlayer
|
||||
channels={parseTraxChannels(s.track)}
|
||||
@@ -222,7 +240,9 @@ export function SoundsClient({
|
||||
src={`${baseUrl}${encodeURIComponent(s.code)}.mp3`}
|
||||
className="h-8 max-w-[280px]"
|
||||
onEnded={() => setPlayingId(null)}
|
||||
onError={() => toast.error(`Impossibile riprodurre "${s.code}.mp3" da ${baseUrl}.`)}
|
||||
onError={() =>
|
||||
toast.error(`Impossibile riprodurre "${s.code}.mp3" da ${baseUrl}.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Button size="sm" variant="ghost" onClick={() => setPlayingId(s.id)}>
|
||||
@@ -268,8 +288,8 @@ export function SoundsClient({
|
||||
open={uploadOpen}
|
||||
onOpenChange={setUploadOpen}
|
||||
onUploaded={() => {
|
||||
invalidateSongPickerCache();
|
||||
router.refresh();
|
||||
invalidateSongPickerCache()
|
||||
router.refresh()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -279,9 +299,9 @@ export function SoundsClient({
|
||||
soundtrack={editing}
|
||||
onOpenChange={(open) => !open && setEditing(null)}
|
||||
onSaved={() => {
|
||||
invalidateSongPickerCache();
|
||||
setEditing(null);
|
||||
router.refresh();
|
||||
invalidateSongPickerCache()
|
||||
setEditing(null)
|
||||
router.refresh()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -294,17 +314,17 @@ export function SoundsClient({
|
||||
<DialogDescription>
|
||||
{deleting.usedBy > 0 ? (
|
||||
<>
|
||||
Questo song disk è attualmente usato da <strong>{deleting.usedBy}</strong> catalog item
|
||||
{deleting.usedBy === 1 ? "" : "s"}. Eliminandolo, quei catalog item verranno riportati a
|
||||
“nessun song disk”.
|
||||
{deleting.kind === "single" && <> Il file MP3 sarà rimosso dal disco.</>} L'azione è
|
||||
irreversibile.
|
||||
Questo song disk è attualmente usato da <strong>{deleting.usedBy}</strong>{' '}
|
||||
catalog item{deleting.usedBy === 1 ? '' : 's'}. Eliminandolo, quei catalog item
|
||||
verranno riportati a “nessun song disk”.
|
||||
{deleting.kind === 'single' && <> Il file MP3 sarà rimosso dal disco.</>}{' '}
|
||||
L'azione è irreversibile.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{deleting.kind === "single"
|
||||
? "Il file MP3 sarà rimosso dal disco e la riga dal database."
|
||||
: "La riga sarà rimossa dal database. Nessun file viene eliminato (una composition non ha MP3 dedicato)."}{" "}
|
||||
{deleting.kind === 'single'
|
||||
? 'Il file MP3 sarà rimosso dal disco e la riga dal database.'
|
||||
: 'La riga sarà rimossa dal database. Nessun file viene eliminato (una composition non ha MP3 dedicato).'}{' '}
|
||||
L'azione è irreversibile.
|
||||
</>
|
||||
)}
|
||||
@@ -323,7 +343,7 @@ export function SoundsClient({
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// ── Upload dialog ────────────────────────────────────────────────────
|
||||
@@ -333,55 +353,55 @@ function UploadDialog({
|
||||
onOpenChange,
|
||||
onUploaded,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onUploaded: () => void;
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onUploaded: () => void
|
||||
}) {
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [author, setAuthor] = useState("");
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const [name, setName] = useState('')
|
||||
const [author, setAuthor] = useState('')
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
function reset() {
|
||||
setName("");
|
||||
setAuthor("");
|
||||
if (fileRef.current) fileRef.current.value = "";
|
||||
setName('')
|
||||
setAuthor('')
|
||||
if (fileRef.current) fileRef.current.value = ''
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const file = fileRef.current?.files?.[0];
|
||||
e.preventDefault()
|
||||
const file = fileRef.current?.files?.[0]
|
||||
if (!file) {
|
||||
toast.error("Seleziona un file MP3.");
|
||||
return;
|
||||
toast.error('Seleziona un file MP3.')
|
||||
return
|
||||
}
|
||||
if (!name.trim()) {
|
||||
toast.error("Il nome è obbligatorio.");
|
||||
return;
|
||||
toast.error('Il nome è obbligatorio.')
|
||||
return
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
fd.append("name", name.trim());
|
||||
fd.append("author", author.trim());
|
||||
setUploading(true)
|
||||
const fd = new FormData()
|
||||
fd.append('file', file)
|
||||
fd.append('name', name.trim())
|
||||
fd.append('author', author.trim())
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/admin/sounds/upload", { method: "POST", body: fd });
|
||||
const json = (await res.json()) as { ok?: boolean; error?: string; id?: number };
|
||||
const res = await fetch('/api/admin/sounds/upload', { method: 'POST', body: fd })
|
||||
const json = (await res.json()) as { ok?: boolean; error?: string; id?: number }
|
||||
if (!res.ok || !json.ok) {
|
||||
toast.error(json.error || "Upload fallito.");
|
||||
return;
|
||||
toast.error(json.error || 'Upload fallito.')
|
||||
return
|
||||
}
|
||||
toast.success(`Song disk #${json.id} caricato.`);
|
||||
reset();
|
||||
onOpenChange(false);
|
||||
onUploaded();
|
||||
toast.success(`Song disk #${json.id} caricato.`)
|
||||
reset()
|
||||
onOpenChange(false)
|
||||
onUploaded()
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Errore di rete durante l'upload.");
|
||||
console.error(err)
|
||||
toast.error("Errore di rete durante l'upload.")
|
||||
} finally {
|
||||
setUploading(false);
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,15 +409,15 @@ function UploadDialog({
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(o) => {
|
||||
if (!uploading) onOpenChange(o);
|
||||
if (!uploading) onOpenChange(o)
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Upload Song Disk</DialogTitle>
|
||||
<DialogDescription>
|
||||
Carica un file MP3. Dimensione massima 10 MB. Il codice sarà generato automaticamente come{" "}
|
||||
<code>song_{id}</code>.
|
||||
Carica un file MP3. Dimensione massima 10 MB. Il codice sarà generato automaticamente
|
||||
come <code>song_{id}</code>.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -429,7 +449,12 @@ function UploadDialog({
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={uploading}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={uploading}
|
||||
>
|
||||
Annulla
|
||||
</Button>
|
||||
<Button type="submit" disabled={uploading}>
|
||||
@@ -444,7 +469,7 @@ function UploadDialog({
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// ── Edit dialog ──────────────────────────────────────────────────────
|
||||
@@ -454,28 +479,28 @@ function EditDialog({
|
||||
onOpenChange,
|
||||
onSaved,
|
||||
}: {
|
||||
soundtrack: SoundtrackRow;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSaved: () => void;
|
||||
soundtrack: SoundtrackRow
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSaved: () => void
|
||||
}) {
|
||||
const [name, setName] = useState(soundtrack.name);
|
||||
const [author, setAuthor] = useState(soundtrack.author);
|
||||
const [track, setTrack] = useState(soundtrack.track ?? "");
|
||||
const [length, setLength] = useState(String(soundtrack.length ?? 0));
|
||||
const { run, isPending } = useServerAction();
|
||||
const [name, setName] = useState(soundtrack.name)
|
||||
const [author, setAuthor] = useState(soundtrack.author)
|
||||
const [track, setTrack] = useState(soundtrack.track ?? '')
|
||||
const [length, setLength] = useState(String(soundtrack.length ?? 0))
|
||||
const { run, isPending } = useServerAction()
|
||||
|
||||
const isComposition = soundtrack.kind === "composition";
|
||||
const isComposition = soundtrack.kind === 'composition'
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
if (!name.trim()) {
|
||||
toast.error("Il nome è obbligatorio.");
|
||||
return;
|
||||
toast.error('Il nome è obbligatorio.')
|
||||
return
|
||||
}
|
||||
const lenNum = Number.parseInt(length, 10);
|
||||
const lenNum = Number.parseInt(length, 10)
|
||||
if (!Number.isFinite(lenNum) || lenNum < 0) {
|
||||
toast.error("La durata deve essere un numero ≥ 0.");
|
||||
return;
|
||||
toast.error('La durata deve essere un numero ≥ 0.')
|
||||
return
|
||||
}
|
||||
run(
|
||||
() =>
|
||||
@@ -483,32 +508,35 @@ function EditDialog({
|
||||
id: soundtrack.id,
|
||||
name: name.trim(),
|
||||
author: author.trim(),
|
||||
track: isComposition ? track : (soundtrack.track ?? ""),
|
||||
track: isComposition ? track : (soundtrack.track ?? ''),
|
||||
length: lenNum,
|
||||
}),
|
||||
{
|
||||
successMessage: "Song disk aggiornato.",
|
||||
errorMessage: "Impossibile aggiornare il song disk.",
|
||||
successMessage: 'Song disk aggiornato.',
|
||||
errorMessage: 'Impossibile aggiornare il song disk.',
|
||||
onSuccess: onSaved,
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(o) => !o && onOpenChange(false)}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Modifica {isComposition ? "Trax Composition" : "Song Disk"}</DialogTitle>
|
||||
<DialogTitle>Modifica {isComposition ? 'Trax Composition' : 'Song Disk'}</DialogTitle>
|
||||
<DialogDescription>
|
||||
<span className="font-mono text-xs">{soundtrack.code}</span>
|
||||
{isComposition ? (
|
||||
<>
|
||||
{" "}
|
||||
— composition di {soundtrack.samples.length} sample. Modifica il campo <code>track</code> solo
|
||||
se sai cosa stai facendo.
|
||||
{' '}
|
||||
— composition di {soundtrack.samples.length} sample. Modifica il campo{' '}
|
||||
<code>track</code> solo se sai cosa stai facendo.
|
||||
</>
|
||||
) : (
|
||||
<> — il file MP3 non può essere sostituito: elimina e ricarica se devi cambiare audio.</>
|
||||
<>
|
||||
{' '}
|
||||
— il file MP3 non può essere sostituito: elimina e ricarica se devi cambiare audio.
|
||||
</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -544,8 +572,8 @@ function EditDialog({
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isComposition
|
||||
? "Durata totale della composition. Lascia 0 se sconosciuta."
|
||||
: "Impostato automaticamente dall’upload. Modifica solo se errato."}
|
||||
? 'Durata totale della composition. Lascia 0 se sconosciuta.'
|
||||
: 'Impostato automaticamente dall’upload. Modifica solo se errato.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -561,15 +589,20 @@ function EditDialog({
|
||||
placeholder="1:371,8;374,4;378,4;0,4;…"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Formato: <code>canale:sample_id,durata;sample_id,durata;…</code> — <code>0</code> = silenzio.
|
||||
Sample IDs rilevati: <span className="font-mono">{soundtrack.samples.join(", ") || "—"}</span>
|
||||
.
|
||||
Formato: <code>canale:sample_id,durata;sample_id,durata;…</code> — <code>0</code> =
|
||||
silenzio. Sample IDs rilevati:{' '}
|
||||
<span className="font-mono">{soundtrack.samples.join(', ') || '—'}</span>.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isPending}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
Annulla
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
@@ -580,5 +613,5 @@ function EditDialog({
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user