diff --git a/packages/frontend/src/routes/play/+page.svelte b/packages/frontend/src/routes/play/+page.svelte index 6bc09a3..37c9a3c 100644 --- a/packages/frontend/src/routes/play/+page.svelte +++ b/packages/frontend/src/routes/play/+page.svelte @@ -20,6 +20,7 @@ import { onMount } from "svelte"; import { goto } from "$app/navigation"; import DeviceCard from "$lib/components/device-card/device-card.svelte"; import RecordingSaveDialog from "./components/recording-save-dialog.svelte"; +import DeviceMappingDialog from "./components/device-mapping-dialog.svelte"; import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types"; import { toast } from "svelte-sonner"; @@ -41,6 +42,8 @@ let playbackProgress = $state(0); let playbackStartTime = $state(null); let playbackTimeoutId = $state(null); let currentEventIndex = $state(0); +let showMappingDialog = $state(false); +let deviceMappings = $state>(new Map()); async function init() { const connector = new ButtplugWasmClientConnector(); @@ -237,11 +240,26 @@ function handleCancelSave() { // Playback functions function startPlayback() { - if (!data.recording || devices.length === 0) { + if (!data.recording) { + return; + } + + if (devices.length === 0) { toast.error("Please connect devices before playing recording"); return; } + // Check if we need to map devices + if (deviceMappings.size === 0 && data.recording.device_info.length > 0) { + showMappingDialog = true; + return; + } + + // Start playback with existing mappings + beginPlayback(); +} + +function beginPlayback() { isPlaying = true; playbackStartTime = performance.now(); playbackProgress = 0; @@ -249,6 +267,16 @@ function startPlayback() { scheduleNextEvent(); } +function handleMappingConfirm(mappings: Map) { + deviceMappings = mappings; + showMappingDialog = false; + beginPlayback(); +} + +function handleMappingCancel() { + showMappingDialog = false; +} + function stopPlayback() { isPlaying = false; if (playbackTimeoutId !== null) { @@ -309,19 +337,19 @@ function scheduleNextEvent() { } function executeEvent(event: RecordedEvent) { - // Find matching device by name - const device = devices.find(d => d.name === event.deviceName); + // Get mapped device + const device = deviceMappings.get(event.deviceName); if (!device) { - console.warn(`Device not found: ${event.deviceName}`); + console.warn(`No device mapping for: ${event.deviceName}`); return; } - // Find matching actuator + // Find matching actuator by type const scalarCmd = device.info.messageAttributes.ScalarCmd.find( cmd => cmd.ActuatorType === event.actuatorType ); if (!scalarCmd) { - console.warn(`Actuator not found: ${event.actuatorType} on ${device.name}`); + console.warn(`Actuator type ${event.actuatorType} not found on ${device.name}`); return; } @@ -576,4 +604,15 @@ onMount(() => { onSave={handleSaveRecording} onCancel={handleCancelSave} /> + + + {#if data.recording} + + {/if} diff --git a/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte b/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte new file mode 100644 index 0000000..3bb0f38 --- /dev/null +++ b/packages/frontend/src/routes/play/components/device-mapping-dialog.svelte @@ -0,0 +1,179 @@ + + + + + + Map Devices for Playback + + Assign your connected devices to match the recorded devices. Only compatible devices are shown. + + + +
+ {#each recordedDevices as recordedDevice} + {@const compatibleDevices = getCompatibleDevices(recordedDevice)} + {@const currentMapping = mappings.get(recordedDevice.name)} + +
+
+
+ +

{recordedDevice.name}

+
+
+ {#each recordedDevice.capabilities as capability} + + {capability} + + {/each} +
+
+ +
+ +
+ {#if compatibleDevices.length === 0} +
+ + No compatible devices +
+ {:else} + { + if (selected?.value) { + handleDeviceSelect(recordedDevice.name, selected.value); + } + }} + > + + + + + {#each compatibleDevices as device} + +
+ + {device.name} + {#if device.name === recordedDevice.name} + + {/if} +
+
+ {/each} +
+
+ {/if} +
+
+ {/each} + + {#if recordedDevices.length === 0} +
+ No devices in this recording +
+ {/if} +
+ + + + + +
+