// AVA — Presets tab: load/delete preset scripts, download the original .avs // binaries for the built-ins, save the current editor script as a preset. 'use client' import { useEffect, useState } from 'react' import { Download, FilePlus2, Play, Save, Trash2 } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { originalFileUrl, STARTER_TEMPLATE } from '@/lib/ava/presets' import { validateScript } from '@/lib/ava/script' import { refreshPresets, useAvaStore, type PresetItem } from '@/lib/store' import { applyScriptText } from '@/components/ava/audio-source' import { modeOfScript, thinScrollbar } from '@/components/ava/shared' import { useToast } from '@/hooks/use-toast' import { cn } from '@/lib/utils' function PresetCard({ preset }: { preset: PresetItem }) { const { toast } = useToast() const setTab = useAvaStore((s) => s.setTab) const [confirmingDelete, setConfirmingDelete] = useState(false) const [deleting, setDeleting] = useState(false) useEffect(() => { if (!confirmingDelete) return const t = setTimeout(() => setConfirmingDelete(false), 3000) return () => clearTimeout(t) }, [confirmingDelete]) const load = () => { const result = applyScriptText(preset.content) if (result.ok) { toast({ title: 'Preset loaded', description: `"${preset.name}" is now running.` }) } else if (result.error) { toast({ title: 'Preset failed to compile', description: `[${result.error.section}] line ${result.error.line}: ${result.error.message}`, variant: 'destructive', }) setTab('script') } } const remove = async () => { setDeleting(true) try { const res = await fetch(`/api/presets/${preset.id}`, { method: 'DELETE' }) if (!res.ok) throw new Error(`HTTP ${res.status}`) await refreshPresets() toast({ title: 'Preset deleted', description: `"${preset.name}" removed.` }) } catch { toast({ title: 'Delete failed', description: 'Could not remove that preset.', variant: 'destructive' }) } finally { setDeleting(false) setConfirmingDelete(false) } } const original = preset.isBuiltIn ? originalFileUrl(preset.name) : undefined return (

{preset.name}

by {preset.author || 'unknown'}

{modeOfScript(preset.content)} {preset.isBuiltIn ? 'built-in' : 'custom'}

{preset.description}

{original && ( )} {!preset.isBuiltIn && ( )}
) } function SavePresetSection() { const { toast } = useToast() const scriptText = useAvaStore((s) => s.scriptText) const [name, setName] = useState('') const [saving, setSaving] = useState(false) const save = async () => { const trimmed = name.trim() if (!trimmed) { toast({ title: 'Name required', description: 'Give the preset a name before saving.', variant: 'destructive' }) return } const v = validateScript(scriptText) setSaving(true) try { const res = await fetch('/api/presets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: trimmed, content: scriptText, author: v.meta?.author || 'you', description: v.meta?.description || '', }), }) const data = (await res.json().catch(() => null)) as { error?: string; updated?: boolean } | null if (!res.ok) { toast({ title: 'Save failed', description: data?.error ?? `Server responded ${res.status}.`, variant: 'destructive', }) return } await refreshPresets() toast({ title: data?.updated ? 'Preset updated' : 'Preset saved', description: `"${trimmed}" is available in the list above.`, }) setName('') } catch { toast({ title: 'Save failed', description: 'Network error while saving the preset.', variant: 'destructive' }) } finally { setSaving(false) } } return (

Save current editor script as preset

Saved under the script's editor content — reuse it any time from this list.

setName(e.target.value)} placeholder="Preset name" className="h-9 border-zinc-700 bg-zinc-900 font-mono text-xs" onKeyDown={(e) => { if (e.key === 'Enter') void save() }} />
) } export function PresetsTab() { const presets = useAvaStore((s) => s.presets) const setScriptText = useAvaStore((s) => s.setScriptText) const setTab = useAvaStore((s) => s.setTab) // freshness refresh whenever the tab mounts (initial seed happens on app load) useEffect(() => { void refreshPresets() }, []) const builtIns = presets.filter((p) => p.isBuiltIn) const customs = presets.filter((p) => !p.isBuiltIn) return (

{presets.length} preset{presets.length === 1 ? '' : 's'} — classics adapted from the original Winamp AVS files.

{/* long-list handling: capped height + styled scrollbar */}
{builtIns.map((p) => ( ))} {customs.length > 0 && (
Custom
)} {customs.map((p) => ( ))} {presets.length === 0 && (
Loading presets…
)}
) }