import { useMemo } from "react"; import { LuArrowUp, LuArrowDown, LuX, LuTrash2 } from "react-icons/lu"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/Button"; import { Combobox } from "@/components/Combobox"; import { SelectMenuBasic } from "@/components/SelectMenuBasic"; import Card from "@/components/Card"; import FieldLabel from "@/components/FieldLabel"; import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros"; import { KeyboardLayout } from "@/keyboardLayouts"; import { keys, modifiers } from "@/keyboardMappings"; // Filter out modifier keys since they're handled in the modifiers section const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta']; const modifierOptions = Object.keys(modifiers).map(modifier => ({ value: modifier, label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"), })); const groupedModifiers: Record = { Control: modifierOptions.filter(mod => mod.value.startsWith('Control')), Shift: modifierOptions.filter(mod => mod.value.startsWith('Shift')), Alt: modifierOptions.filter(mod => mod.value.startsWith('Alt')), Meta: modifierOptions.filter(mod => mod.value.startsWith('Meta')), }; interface MacroStep { keys: string[]; modifiers: string[]; delay: number; } interface MacroStepCardProps { step: MacroStep; stepIndex: number; onDelete?: () => void; onMoveUp?: () => void; onMoveDown?: () => void; onKeySelect: (option: { value: string | null; keys?: string[] }) => void; onKeyQueryChange: (query: string) => void; keyQuery: string; onModifierChange: (modifiers: string[]) => void; onDelayChange: (delay: number) => void; isLastStep: boolean; keyboard: KeyboardLayout } const ensureArray = (arr: T[] | null | undefined): T[] => { return Array.isArray(arr) ? arr : []; }; export function MacroStepCard({ step, stepIndex, onDelete, onMoveUp, onMoveDown, onKeySelect, onKeyQueryChange, keyQuery, onModifierChange, onDelayChange, isLastStep, keyboard }: MacroStepCardProps) { const { t } = useTranslation(); const basePresetDelays = [ { value: "50", label: t('_ms',{num:50}) }, { value: "100", label: t('_ms',{num:100}) }, { value: "200", label: t('_ms',{num:200}) }, { value: "300", label: t('_ms',{num:300}) }, { value: "500", label: t('_ms',{num:500}) }, { value: "750", label: t('_ms',{num:750}) }, { value: "1000", label: t('_ms',{num:1000}) }, { value: "1500", label: t('_ms',{num:1500}) }, { value: "2000", label: t('_ms',{num:2000}) }, ]; const PRESET_DELAYS = basePresetDelays.map(delay => { if (parseInt(delay.value, 10) === DEFAULT_DELAY) { return { ...delay, label: t('Default') }; } return delay; }); const { keyDisplayMap } = keyboard; const keyOptions = useMemo(() => Object.keys(keys) .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix))) .map(key => ({ value: key, label: keyDisplayMap[key] || key, })), [keyDisplayMap] ); const filteredKeys = useMemo(() => { const selectedKeys = ensureArray(step.keys); const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value)); if (keyQuery === '') { return availableKeys; } else { return availableKeys.filter(option => option.label.toLowerCase().includes(keyQuery.toLowerCase())); } }, [keyOptions, keyQuery, step.keys]); return (
{stepIndex + 1}
{onDelete && (
{Object.entries(groupedModifiers).map(([group, mods]) => (
{t(group)}
{mods.map(option => (
))}
{ensureArray(step.keys) && step.keys.length > 0 && (
{step.keys.map((key, keyIndex) => ( {keyDisplayMap[key] || key}
)}
{ onKeySelect({ value: value as string | null }); onKeyQueryChange(''); }} displayValue={() => keyQuery} onInputChange={onKeyQueryChange} options={() => filteredKeys} disabledMessage={t('Max_keys_reached')} size="SM" immediate disabled={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP} placeholder={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP ? t('Max_keys_reached') : t('Search_for_key')} emptyMessage={t('No_matching_keys_found')} />
onDelayChange(parseInt(e.target.value, 10))} options={PRESET_DELAYS} />
); }