Files
sexy/components/sessions/DeviceRemapDialog.tsx
T

95 lines
3.3 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { autoMapDeviceSlots } from "@/lib/buttplug/device-remap";
import type { ReplayDeviceSlot, ConnectedDeviceInfo } from "@/lib/buttplug/types";
interface DeviceRemapDialogProps {
open: boolean;
deviceSlots: ReplayDeviceSlot[];
connectedDevices: ConnectedDeviceInfo[];
onCancel: () => void;
onConfirm: (mapping: Map<number, number>) => void;
}
export function DeviceRemapDialog({
open,
deviceSlots,
connectedDevices,
onCancel,
onConfirm,
}: DeviceRemapDialogProps) {
const autoMapped = autoMapDeviceSlots(deviceSlots, connectedDevices);
const [assignments, setAssignments] = useState<Record<number, number | null>>(
Object.fromEntries(deviceSlots.map((slot, i) => [slot.sourceSessionDeviceId, autoMapped[i]?.matchedDeviceIndex ?? null])),
);
const allAssigned = deviceSlots.every((s) => assignments[s.sourceSessionDeviceId] !== null);
function handleConfirm() {
const mapping = new Map<number, number>();
for (const slot of deviceSlots) {
const deviceIndex = assignments[slot.sourceSessionDeviceId];
if (deviceIndex !== null && deviceIndex !== undefined) mapping.set(slot.sourceSessionDeviceId, deviceIndex);
}
onConfirm(mapping);
}
return (
<Dialog open={open} onOpenChange={(o) => !o && onCancel()}>
<DialogContent>
<DialogHeader>
<DialogTitle>Match devices for replay</DialogTitle>
<DialogDescription>
Web Bluetooth doesn&apos;t expose a stable device id across sessions, so match each recorded
device to a currently-connected one. Two identically-named devices can&apos;t be told apart
automatically.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
{deviceSlots.map((slot) => (
<div key={slot.sourceSessionDeviceId} className="flex items-center justify-between gap-3">
<span className="text-sm font-medium">{slot.slotLabel}</span>
<Select
value={assignments[slot.sourceSessionDeviceId]?.toString() ?? undefined}
onValueChange={(v) =>
setAssignments((prev) => ({ ...prev, [slot.sourceSessionDeviceId]: Number(v) }))
}
>
<SelectTrigger size="sm" className="w-48">
<SelectValue placeholder="Unmatched" />
</SelectTrigger>
<SelectContent>
{connectedDevices.map((d) => (
<SelectItem key={d.index} value={d.index.toString()}>
{d.displayName ?? d.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
))}
</div>
<DialogFooter>
<Button variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button onClick={handleConfirm} disabled={!allAssigned}>
Start replay
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}