import type { Torrent } from '../shared'; import type { FormEvent } from 'react'; import { useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, 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 { LIMIT_MODE, PRIORITY } from '../shared'; import { useTorrentMutations } from '../useTransmissionData'; // Per-torrent bandwidth, seeding and peer options — the reference client's "other settings" dialog. // // Every limit here is a pair (a boolean and a number) because Transmission stores them that way: turning a // limit off keeps the number, so unchecking and re-checking gets you back the value you had. type TorrentOptionsDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; targets: Torrent[] }; type FormState = { bandwidthPriority: string; honorsSessionLimits: boolean; downloadLimited: boolean; downloadLimit: string; uploadLimited: boolean; uploadLimit: string; seedRatioMode: string; seedRatioLimit: string; seedIdleMode: string; seedIdleLimit: string; peerLimit: string; sequential: boolean; }; const fromTorrent = (t: Torrent): FormState => ({ bandwidthPriority: String(t.bandwidthPriority ?? 0), honorsSessionLimits: t.honorsSessionLimits ?? true, downloadLimited: t.downloadLimited ?? false, downloadLimit: String(t.downloadLimit ?? 0), uploadLimited: t.uploadLimited ?? false, uploadLimit: String(t.uploadLimit ?? 0), seedRatioMode: String(t.seedRatioMode ?? 0), seedRatioLimit: String(t.seedRatioLimit ?? 2), seedIdleMode: String(t.seedIdleMode ?? 0), seedIdleLimit: String(t.seedIdleLimit ?? 30), peerLimit: String(t['peer-limit'] ?? 50), sequential: t.sequential_download ?? false, }); export const TorrentOptionsDialog = ({ open, onOpenChange, targets }: TorrentOptionsDialogProps) => { const { set } = useTorrentMutations(); const [form, setForm] = useState(null); useEffect(() => { // Seeded from the first target. With a mixed selection the others' values are simply overwritten — // which is what "apply these settings to all of them" means, and the header says so. if (open && targets[0]) setForm(fromTorrent(targets[0])); }, [open, targets]); if (!form) return null; const patch = (next: Partial) => setForm((prev) => (prev ? { ...prev, ...next } : prev)); const submit = async (ev?: FormEvent) => { ev?.preventDefault(); await set.mutateAsync({ ids: targets.map((t) => t.id), bandwidthPriority: Number(form.bandwidthPriority), honorsSessionLimits: form.honorsSessionLimits, downloadLimited: form.downloadLimited, downloadLimit: Math.max(0, Number(form.downloadLimit) || 0), uploadLimited: form.uploadLimited, uploadLimit: Math.max(0, Number(form.uploadLimit) || 0), seedRatioMode: Number(form.seedRatioMode), seedRatioLimit: Math.max(0, Number(form.seedRatioLimit) || 0), seedIdleMode: Number(form.seedIdleMode), seedIdleLimit: Math.max(0, Number(form.seedIdleLimit) || 0), 'peer-limit': Math.max(1, Number(form.peerLimit) || 1), sequential_download: form.sequential, }); onOpenChange(false); }; return ( Torrent options {targets.length === 1 ? targets[0]!.name : `Applying to ${targets.length} torrents — all of them will take these values.`}

Bandwidth

patch({ peerLimit: ev.target.value })} />
patch({ downloadLimited: v })} onChange={(v) => patch({ downloadLimit: v })} /> patch({ uploadLimited: v })} onChange={(v) => patch({ uploadLimit: v })} />

Stop seeding when

patch({ seedRatioLimit: ev.target.value })} />
patch({ seedIdleLimit: ev.target.value })} />
); }; type LimitRowProps = { id: string; label: string; enabled: boolean; value: string; onToggle: (enabled: boolean) => void; onChange: (value: string) => void; }; const LimitRow = ({ id, label, enabled, value, onToggle, onChange }: LimitRowProps) => (
onChange(ev.target.value)} className="w-28" /> kB/s
);