import React, { useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Camera, Cast, Loader2, X } from 'lucide-react'; import type { InternetRadioStation } from '@/api/subsonicTypes'; import { CoverArtImage } from '@/cover/CoverArtImage'; import { albumCoverRef } from '@/cover/ref'; import { coverArtIdFromRadio } from '@/cover/ids'; interface RadioEditModalProps { station: InternetRadioStation | null; // null = create new onClose: () => void; onSave: (opts: { name: string; streamUrl: string; homepageUrl: string; coverFile: File | null; coverRemoved: boolean; }) => Promise; } export default function RadioEditModal({ station, onClose, onSave }: RadioEditModalProps) { const { t } = useTranslation(); const [name, setName] = useState(station?.name ?? ''); const [streamUrl, setStreamUrl] = useState(station?.streamUrl ?? ''); const [homepageUrl, setHomepageUrl] = useState(station?.homepageUrl ?? ''); const [coverFile, setCoverFile] = useState(null); const [coverPreview, setCoverPreview] = useState(null); const [coverRemoved, setCoverRemoved] = useState(false); const [saving, setSaving] = useState(false); const coverInputRef = useRef(null); const hasExistingCover = !coverRemoved && (coverPreview || station?.coverArt); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; e.target.value = ''; if (!file) return; setCoverFile(file); setCoverRemoved(false); const reader = new FileReader(); reader.onload = ev => setCoverPreview(ev.target?.result as string); reader.readAsDataURL(file); }; const handleRemoveCover = (e: React.MouseEvent) => { e.stopPropagation(); setCoverFile(null); setCoverPreview(null); setCoverRemoved(true); }; const handleSave = async () => { if (!name.trim() || !streamUrl.trim()) return; setSaving(true); try { await onSave({ name, streamUrl, homepageUrl, coverFile, coverRemoved }); } finally { setSaving(false); } }; const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) onClose(); }; /* Portal to document.body: nested .content-body uses contain:paint — in-tree * modals are clipped when the station list is empty (short layout). Same pattern as RadioDirectoryModal. */ return createPortal(
e.stopPropagation()} >

{station ? t('radio.editStation') : t('radio.addStation')}

{/* Cover + fields side by side */}
{/* Cover */}
coverInputRef.current?.click()} > {coverPreview ? ( ) : !coverRemoved && station?.coverArt ? ( ) : (
)}
{hasExistingCover && ( )}
{/* Fields */}
setName(e.target.value)} placeholder={t('radio.stationName')} autoFocus /> setStreamUrl(e.target.value)} placeholder={t('radio.streamUrl')} /> setHomepageUrl(e.target.value)} placeholder={t('radio.homepageUrl')} />
, document.body ); }