diff --git a/frontend/src/components/ConfigureAmsSlotModal.tsx b/frontend/src/components/ConfigureAmsSlotModal.tsx index ef83191fd..dad4ddb23 100644 --- a/frontend/src/components/ConfigureAmsSlotModal.tsx +++ b/frontend/src/components/ConfigureAmsSlotModal.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useEffect, useCallback } from 'react'; +import { useState, useMemo, useEffect, useCallback, useRef } from 'react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { X, Loader2, Settings2, ChevronDown, CheckCircle2, RotateCcw } from 'lucide-react'; @@ -256,6 +256,7 @@ export function ConfigureAmsSlotModal({ const [searchQuery, setSearchQuery] = useState(''); const [showSuccess, setShowSuccess] = useState(false); const [showExtendedColors, setShowExtendedColors] = useState(false); + const scrolledToRef = useRef(''); // Fetch cloud settings (gracefully handle 401 when logged out) const { data: cloudSettings, isLoading: settingsLoading, isError: cloudError } = useQuery({ @@ -739,6 +740,13 @@ export function ConfigureAmsSlotModal({ if (currentPreset) { setSelectedPresetId(currentPreset.setting_id); } + } else if (slotInfo.trayInfoIdx && builtinFilaments?.length) { + // Last resort: match trayInfoIdx against builtin presets + const trayIdx = slotInfo.trayInfoIdx; + const match = builtinFilaments.find(bf => bf.filament_id === trayIdx); + if (match) { + setSelectedPresetId(`builtin_${match.filament_id}`); + } } // Pre-populate color from current slot (black is valid — empty slots don't pass trayColor) @@ -756,8 +764,9 @@ export function ConfigureAmsSlotModal({ setColorInput(''); setSearchQuery(''); setShowSuccess(false); + scrolledToRef.current = ''; } - }, [isOpen, slotInfo.savedPresetId, slotInfo.trayInfoIdx, slotInfo.trayColor, cloudSettings?.filament]); + }, [isOpen, slotInfo.savedPresetId, slotInfo.trayInfoIdx, slotInfo.trayColor, cloudSettings?.filament, builtinFilaments]); // Auto-select best matching K profile when preset changes useEffect(() => { @@ -791,9 +800,26 @@ export function ConfigureAmsSlotModal({ } }, [isOpen, handleKeyDown]); - if (!isOpen) return null; - const isLoading = (settingsLoading && !cloudError) || localLoading || builtinLoading || kprofilesLoading; + + // Scroll selected preset into view when data finishes loading or the selection changes. + // Uses a ref guard so scrollIntoView only fires once per selection, preventing the + // infinite scroll loop that occurred on Windows with inline callback refs. + useEffect(() => { + if (!isLoading && selectedPresetId && selectedPresetId !== scrolledToRef.current) { + const raf = requestAnimationFrame(() => { + const modal = document.querySelector('[class*="fixed inset-0 z-50"]'); + const el = modal?.querySelector(`[data-preset-id="${CSS.escape(selectedPresetId)}"]`); + if (el) { + scrolledToRef.current = selectedPresetId; + el.scrollIntoView({ block: 'nearest' }); + } + }); + return () => cancelAnimationFrame(raf); + } + }, [selectedPresetId, isLoading]); + + if (!isOpen) return null; const canSave = selectedPresetId && !configureMutation.isPending; // Get display color (custom or slot default) @@ -910,9 +936,7 @@ export function ConfigureAmsSlotModal({ filteredPresets.map((preset) => (