From 669e4244a6bc3f37084b3f4fa230060f04d80530 Mon Sep 17 00:00:00 2001
From: Andrew Davis <1709934+Savid@users.noreply.github.com>
Date: Wed, 2 Apr 2025 23:29:41 +1000
Subject: [PATCH] use existing components and CTA
---
 ui/src/routes/devices.$id.settings.macros.tsx | 934 +++++++++---------
 1 file changed, 450 insertions(+), 484 deletions(-)
diff --git a/ui/src/routes/devices.$id.settings.macros.tsx b/ui/src/routes/devices.$id.settings.macros.tsx
index 35c86fdd..9896c5c1 100644
--- a/ui/src/routes/devices.$id.settings.macros.tsx
+++ b/ui/src/routes/devices.$id.settings.macros.tsx
@@ -1,14 +1,19 @@
 import { useState, useEffect, useRef, useCallback } from "react";
-import { LuPlus, LuTrash, LuX, LuPenLine, LuLoader, LuGripVertical, LuInfo } from "react-icons/lu";
+import { LuPlus, LuTrash, LuX, LuPenLine, LuLoader, LuGripVertical, LuInfo, LuCopy, LuArrowUp, LuArrowDown } from "react-icons/lu";
 import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
 
 import { KeySequence, useMacrosStore } from "../hooks/stores";
-import { SettingsPageHeader } from "../components/SettingsPageheader";
-import { Button } from "../components/Button";
+import { SettingsPageHeader } from "@/components/SettingsPageheader";
+import { Button } from "@/components/Button";
+import Checkbox from "@/components/Checkbox";
 import { keys, modifiers } from "../keyboardMappings";
 import { useJsonRpc } from "../hooks/useJsonRpc";
 import notifications from "../notifications";
 import { SettingsItem } from "../routes/devices.$id.settings";
+import { InputFieldWithLabel, FieldError } from "@/components/InputField";
+import Fieldset from "@/components/Fieldset";
+import { SelectMenuBasic } from "@/components/SelectMenuBasic";
+import EmptyCard from "@/components/EmptyCard";
 
 const DEFAULT_DELAY = 50;
 
@@ -109,15 +114,15 @@ function KeyCombobox({
 }
 
 const PRESET_DELAYS = [
-  { value: 50, label: "50ms" },
-  { value: 100, label: "100ms" },
-  { value: 200, label: "200ms" },
-  { value: 300, label: "300ms" },
-  { value: 500, label: "500ms" },
-  { value: 750, label: "750ms" },
-  { value: 1000, label: "1000ms" },
-  { value: 1500, label: "1500ms" },
-  { value: 2000, label: "2000ms" },
+  { value: "50", label: "50ms" },
+  { value: "100", label: "100ms" },
+  { value: "200", label: "200ms" },
+  { value: "300", label: "300ms" },
+  { value: "500", label: "500ms" },
+  { value: "750", label: "750ms" },
+  { value: "1000", label: "1000ms" },
+  { value: "1500", label: "1500ms" },
+  { value: "2000", label: "2000ms" },
 ];
 
 const MAX_STEPS_PER_MACRO = 10;
@@ -171,26 +176,20 @@ function MacroStepCard({
       
         
           
-            
-              
-                 
-             
-            
+            
-              
-                 
-             
+              LeadingIcon={LuArrowDown}
+            />
           
             {stepIndex + 1}
@@ -199,18 +198,13 @@ function MacroStepCard({
         
         
           {onDelete && (
-            
-              
-                 
-              Delete 
-             
+              LeadingIcon={LuTrash}
+              />
           )}
         
  
@@ -236,9 +230,9 @@ function MacroStepCard({
                           : 'bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700'
                       }`}
                     >
-                      
 {
                           const modifiersArray = ensureArray(step.modifiers);
@@ -266,19 +260,20 @@ function MacroStepCard({
             {ensureArray(step.keys).map((key, keyIndex) => (
               
-                {key}
-                
+                  {key}
+                  
+                
 {
                     const newKeys = ensureArray(step.keys).filter((_, i) => i !== keyIndex);
                     onKeySelect({ value: null, keys: newKeys });
                   }}
-                >
-                  ×
-                 
+                  LeadingIcon={LuX}
+                />
               
             ))}
           
 
@@ -313,17 +308,13 @@ function MacroStepCard({
             
           
           
-             onDelayChange(parseInt(e.target.value, 10))}
-            >
-              {PRESET_DELAYS.map((option) => (
-                
-                  {option.label}
-                 
-              ))}
-             
+              options={PRESET_DELAYS}
+            />
           
         
       
@@ -928,52 +919,38 @@ export default function SettingsMacrosRoute() {
   const ErrorMessage = ({ error }: { error?: string }) => {
     if (!error) return null;
     return (
-      
-        {error}
-      
+      
-      
-
-      {errorMessage && (
-        
-          
-            
-            
-              
{errorMessage} 
-            
+          
+        {macros.length > 0 && (
+          
+            
+              
+                {!showAddMacro && (
+                   setShowAddMacro(true)}
+                    disabled={isMaxMacrosReached}
+                  />
+                )}
+               
+             
           
-        
-      )}
+        )}
 
-      
-        
-          
-            {!showAddMacro && (
-               setShowAddMacro(true)}
-                disabled={isMaxMacrosReached}
-              />
-            )}
-           
-         
-      
+      {errorMessage && (
)}
 
       {loading && (
         
@@ -986,12 +963,14 @@ export default function SettingsMacrosRoute() {
             
               
Add New Macro 
             
-            
-              
-                
+              
+                 {
                     setNewMacro(prev => ({ ...prev, name: e.target.value }));
                     if (errors.name) {
@@ -1000,15 +979,13 @@ export default function SettingsMacrosRoute() {
                       setErrors(newErrors);
                     }
                   }}
-                  placeholder="Macro Name"
                 />
-                 
-              
-                
-            
+            
             
             
               
@@ -1040,396 +1015,387 @@ export default function SettingsMacrosRoute() {
               
                 You can add up to {MAX_STEPS_PER_MACRO} steps per macro
               
-              
-                {(newMacro.steps || []).map((step, stepIndex) => (
-                  
 1 ? () => {
-                      const newSteps = [...(newMacro.steps || [])];
-                      newSteps.splice(stepIndex, 1);
-                      setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                    } : undefined}
-                    onMoveUp={() => {
-                      const newSteps = handleStepMove(stepIndex, 'up', newMacro.steps || []);
-                      setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                    }}
-                    onMoveDown={() => {
-                      const newSteps = handleStepMove(stepIndex, 'down', newMacro.steps || []);
-                      setNewMacro(prev => ({ ...prev, steps: newSteps }));
-                    }}
-                    isDesktop={isDesktop}
-                    onKeySelect={(option) => handleKeySelect(stepIndex, option)}
-                    onKeyQueryChange={(query) => handleKeyQueryChange(stepIndex, query)}
-                    keyQuery={keyQueries[stepIndex] || ''}
-                    getFilteredKeys={() => getFilteredKeys(stepIndex)}
-                    onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
-                    onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
-                    isLastStep={stepIndex === (newMacro.steps?.length || 0) - 1}
-                  />
-                ))}
-
-                
-                  
 {
-                      if (isMaxStepsReachedForNewMacro) {
-                        showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
-                        return;
-                      }
-                      
-                      setNewMacro(prev => ({
-                        ...prev,
-                        steps: [
-                          ...(prev.steps || []), 
-                          { keys: [], modifiers: [], delay: DEFAULT_DELAY }
-                        ],
-                      }));
-                      clearErrors();
-                    }}
-                    disabled={isMaxStepsReachedForNewMacro}
-                  >
-                    Add Step {isMaxStepsReachedForNewMacro && `(${MAX_STEPS_PER_MACRO} max)`} 
-                   
+              
+                
+                  {(newMacro.steps || []).map((step, stepIndex) => (
+                     1 ? () => {
+                        const newSteps = [...(newMacro.steps || [])];
+                        newSteps.splice(stepIndex, 1);
+                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
+                      } : undefined}
+                      onMoveUp={() => {
+                        const newSteps = handleStepMove(stepIndex, 'up', newMacro.steps || []);
+                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
+                      }}
+                      onMoveDown={() => {
+                        const newSteps = handleStepMove(stepIndex, 'down', newMacro.steps || []);
+                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
+                      }}
+                      isDesktop={isDesktop}
+                      onKeySelect={(option) => handleKeySelect(stepIndex, option)}
+                      onKeyQueryChange={(query) => handleKeyQueryChange(stepIndex, query)}
+                      keyQuery={keyQueries[stepIndex] || ''}
+                      getFilteredKeys={() => getFilteredKeys(stepIndex)}
+                      onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
+                      onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
+                      isLastStep={stepIndex === (newMacro.steps?.length || 0) - 1}
+                    />
+                  ))}
                  
+               
 
-                
-                  {showClearConfirm ? (
-                    
-                      
-                        Cancel changes?
-                       
-                      
 {
+              
+                 {
+                    if (isMaxStepsReachedForNewMacro) {
+                      showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
+                      return;
+                    }
+                    
+                    setNewMacro(prev => ({
+                      ...prev,
+                      steps: [
+                        ...(prev.steps || []), 
+                        { keys: [], modifiers: [], delay: DEFAULT_DELAY }
+                      ],
+                    }));
+                    clearErrors();
+                  }}
+                  disabled={isMaxStepsReachedForNewMacro}
+                />
+               
+
+              
+                {showClearConfirm ? (
+                  
+                    
+                      Cancel changes?
+                     
+                     {
+                        resetNewMacro();
+                        setShowAddMacro(false);
+                        setShowClearConfirm(false);
+                      }}
+                    />
+                     setShowClearConfirm(false)}
+                    />
+                    
+                ) : (
+                  
+                     {
+                        if (newMacro.name || newMacro.description || newMacro.steps?.some(s => s.keys?.length || s.modifiers?.length)) {
+                          setShowClearConfirm(true);
+                        } else {
                           resetNewMacro();
                           setShowAddMacro(false);
-                          setShowClearConfirm(false);
-                        }}
-                      />
-                       setShowClearConfirm(false)}
-                      />
-                      
-                  ) : (
-                    
-                       {
-                          if (newMacro.name || newMacro.description || newMacro.steps?.some(s => s.keys?.length || s.modifiers?.length)) {
-                            setShowClearConfirm(true);
-                          } else {
-                            resetNewMacro();
-                            setShowAddMacro(false);
-                          }
-                        }}
-                      />
-                     
-                  )}
-                
 
+                )}
               
 
         )}
+        {macros.length === 0 && !showAddMacro && (
+          
 setShowAddMacro(true)}
+                disabled={isMaxMacrosReached}
+              />
+            }
+          />
+        )}
         {macros.length > 0 && (
-          
-            
-            {macros.length === 0 ? (
-              
-                No macros created yet. Add your first macro above.
-              
-            ) : (
-              
-                {macros.map((macro, index) => 
-                  editingMacro && editingMacro.id === macro.id ? (
-                    
-                      
-                        
-                          
-                        
-                          
+          
+            {macros.map((macro, index) => 
+              editingMacro && editingMacro.id === macro.id ? (
+                
+                  
+                    
+                       {
+                          setEditingMacro({ ...editingMacro, name: e.target.value });
+                          if (errors.name) {
+                            const newErrors = { ...errors };
+                            delete newErrors.name;
+                            setErrors(newErrors);
+                          }
+                        }}
+                      />
+                       {
+                          setEditingMacro({ ...editingMacro, description: e.target.value });
+                          if (errors.description) {
+                            const newErrors = { ...errors };
+                            delete newErrors.description;
+                            setErrors(newErrors);
+                          }
+                        }}
+                      />
+                      
+                   
+                  
+                  
+                    
+                      
+                        Steps:
+                       
+                      
+                        {editingMacro.steps.length}/{MAX_STEPS_PER_MACRO} steps
+                       
+                    
+                    {errors.steps && errors.steps[0]?.keys && (
+                      
+                        
-                      
-                      
-                        
-                          
-                            Steps:
-                           
-                          
-                            {editingMacro.steps.length}/{MAX_STEPS_PER_MACRO} steps
-                           
-                        
-                        {errors.steps && errors.steps[0]?.keys && (
-                          
-                            
-                        )}
-                        
-                          You can add up to {MAX_STEPS_PER_MACRO} steps per macro
-                        
-                        
-                          {editingMacro.steps.map((step, stepIndex) => (
-                            
 1 ? () => {
-                                const newSteps = [...editingMacro.steps];
-                                newSteps.splice(stepIndex, 1);
-                                setEditingMacro({ ...editingMacro, steps: newSteps });
-                              } : undefined}
-                              onMoveUp={() => {
-                                const newSteps = handleStepMove(stepIndex, 'up', editingMacro.steps);
-                                setEditingMacro({ ...editingMacro, steps: newSteps });
-                              }}
-                              onMoveDown={() => {
-                                const newSteps = handleStepMove(stepIndex, 'down', editingMacro.steps);
-                                setEditingMacro({ ...editingMacro, steps: newSteps });
-                              }}
-                              isDesktop={isDesktop}
-                              onKeySelect={(option) => handleEditKeySelect(stepIndex, option)}
-                              onKeyQueryChange={(query) => handleEditKeyQueryChange(stepIndex, query)}
-                              keyQuery={editKeyQueries[stepIndex] || ''}
-                              getFilteredKeys={() => getFilteredKeys(stepIndex, true)}
-                              onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
-                              onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
-                              isLastStep={stepIndex === editingMacro.steps.length - 1}
-                            />
-                          ))}
+                    )}
+                    
+                      You can add up to {MAX_STEPS_PER_MACRO} steps per macro
+                    
+                    
+                      
+                        {editingMacro.steps.map((step, stepIndex) => (
+                           1 ? () => {
+                              const newSteps = [...editingMacro.steps];
+                              newSteps.splice(stepIndex, 1);
+                              setEditingMacro({ ...editingMacro, steps: newSteps });
+                            } : undefined}
+                            onMoveUp={() => {
+                              const newSteps = handleStepMove(stepIndex, 'up', editingMacro.steps);
+                              setEditingMacro({ ...editingMacro, steps: newSteps });
+                            }}
+                            onMoveDown={() => {
+                              const newSteps = handleStepMove(stepIndex, 'down', editingMacro.steps);
+                              setEditingMacro({ ...editingMacro, steps: newSteps });
+                            }}
+                            isDesktop={isDesktop}
+                            onKeySelect={(option) => handleEditKeySelect(stepIndex, option)}
+                            onKeyQueryChange={(query) => handleEditKeyQueryChange(stepIndex, query)}
+                            keyQuery={editKeyQueries[stepIndex] || ''}
+                            getFilteredKeys={() => getFilteredKeys(stepIndex, true)}
+                            onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
+                            onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
+                            isLastStep={stepIndex === editingMacro.steps.length - 1}
+                          />
+                        ))}
+                       
+                     
+                    
+                    
+                      
= MAX_STEPS_PER_MACRO ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
+                        onClick={() => {
+                          if (editingMacro.steps.length >= MAX_STEPS_PER_MACRO) {
+                            showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
+                            return;
+                          }
                           
-                          
-                            = MAX_STEPS_PER_MACRO
-                                  ? 'bg-slate-100 text-slate-400 cursor-not-allowed dark:bg-slate-800 dark:text-slate-500'
-                                  : 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700'
-                              }`}
-                              onClick={() => {
-                                if (editingMacro.steps.length >= MAX_STEPS_PER_MACRO) {
-                                  showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
-                                  return;
-                                }
-                                
-                                setEditingMacro({
-                                  ...editingMacro,
-                                  steps: [
-                                    ...editingMacro.steps, 
-                                    { keys: [], modifiers: [], delay: DEFAULT_DELAY }
-                                  ],
-                                });
-                                clearErrors();
-                              }}
-                              disabled={editingMacro.steps.length >= MAX_STEPS_PER_MACRO}
-                            >
-                              Add Step {editingMacro.steps.length >= MAX_STEPS_PER_MACRO && `(${MAX_STEPS_PER_MACRO} max)`} 
-                             
-                          
-                         
-                          
-                             {
-                                setEditingMacro(null);
-                                setErrors({});
-                              }}
-                            />
-                           
-                        
 
+
+                    
+                      
+                         {
+                            setEditingMacro(null);
+                            setErrors({});
+                          }}
+                        />
                        
                     
-                  ) : (
-                    
 handleDragStart(index)}
-                      onDragOver={e => handleDragOver(e, index)}
-                      onDragEnd={() => {
-                        const allItems = document.querySelectorAll('[data-macro-item]');
-                        allItems.forEach(el => {
-                          el.classList.remove('drop-target');
-                          el.classList.remove('dragging');
-                        });
-                        setIsDragging(false);
-                      }}
-                      onDrop={handleDrop}
-                      onTouchStart={(e) => handleTouchStart(e, index)}
-                      onTouchMove={handleTouchMove}
-                      onTouchEnd={handleTouchEnd}
-                      className={`macro-sortable flex items-center justify-between rounded-md border border-slate-200 p-2 dark:border-slate-700 ${
-                        isDragging && dragItem.current === index
-                          ? "bg-blue-50 dark:bg-blue-900/20"
-                          : "bg-white dark:bg-slate-800"
-                      }`}
-                    >
-                      
-                        
-                      
-                      
-                        
-                          {macro.name}
-                         
-                        {macro.description && (
-                          
-                            {macro.description}
-                          
-                        )}
-                        
-                          
-                            {macro.steps.slice(0, 3).map((step, stepIndex) => {
-                              const modifiersText = ensureArray(step.modifiers).length > 0 
-                                ? ensureArray(step.modifiers).map(m => m.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1")).join(' + ')
-                                : '';
-                              
-                              const keysText = ensureArray(step.keys).length > 0 ? ensureArray(step.keys).join(' + ') : '';
-                              const combinedText = (modifiersText || keysText) 
-                                ? [modifiersText, keysText].filter(Boolean).join(' + ')
-                                : 'Delay only';
-                              
-                              return (
-                                
-                                  {stepIndex > 0 && → }
-                                  
-                                    {combinedText} 
-                                    ({step.delay}ms) 
-                                   
-                                 
-                              );
-                            })}
-                            {macro.steps.length > 3 && (
-                              
-                                + {macro.steps.length - 3} more steps
+                    
+                
+              ) : (
+                
 handleDragStart(index)}
+                  onDragOver={e => handleDragOver(e, index)}
+                  onDragEnd={() => {
+                    const allItems = document.querySelectorAll('[data-macro-item]');
+                    allItems.forEach(el => {
+                      el.classList.remove('drop-target');
+                      el.classList.remove('dragging');
+                    });
+                    setIsDragging(false);
+                  }}
+                  onDrop={handleDrop}
+                  onTouchStart={(e) => handleTouchStart(e, index)}
+                  onTouchMove={handleTouchMove}
+                  onTouchEnd={handleTouchEnd}
+                  className={`macro-sortable flex items-center justify-between rounded-md border border-slate-200 p-2 dark:border-slate-700 ${
+                    isDragging && dragItem.current === index
+                      ? "bg-blue-50 dark:bg-blue-900/20"
+                      : "bg-white dark:bg-slate-800"
+                  }`}
+                >
+                  
+                    
+                  
+                  
+                    
+                      {macro.name}
+                     
+                    {macro.description && (
+                      
+                        {macro.description}
+                      
+                    )}
+                    
+                      
+                        {macro.steps.slice(0, 3).map((step, stepIndex) => {
+                          const modifiersText = ensureArray(step.modifiers).length > 0 
+                            ? ensureArray(step.modifiers).map(m => m.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1")).join(' + ')
+                            : '';
+                          
+                          const keysText = ensureArray(step.keys).length > 0 ? ensureArray(step.keys).join(' + ') : '';
+                          const combinedText = (modifiersText || keysText) 
+                            ? [modifiersText, keysText].filter(Boolean).join(' + ')
+                            : 'Delay only';
+                          
+                          return (
+                            
+                              {stepIndex > 0 && → }
+                              
+                                {combinedText} 
+                                ({step.delay}ms) 
                                
-                            )}
-                           
-                         
-                      
-                      
-                      
-                        {macroToDelete === macro.id ? (
-                          
-                            
-                              Delete macro?
                              
-                             {
-                                handleDeleteMacro(macro.id);
-                              }}
-                            />
-                             setMacroToDelete(null)}
-                            />
-                            
-                        ) : (
-                          <>
-                            
 handleEditMacro(macro)}
-                              title="Edit"
-                            >
-                               
-                            
 handleDuplicateMacro(macro)}
-                              title="Duplicate"
-                            >
-                              
-                                 
-                             
-                            
 setMacroToDelete(macro.id)}
-                              title="Delete"
-                            >
-                               
-                          >
+                          );
+                        })}
+                        {macro.steps.length > 3 && (
+                          
+                            + {macro.steps.length - 3} more steps
+                           
                         )}
+                      
+                    
+                  
+                  
+                  
+                    {macroToDelete === macro.id ? (
+                      
+                        
+                          Delete macro?
+                         
+                        
+                           {
+                              handleDeleteMacro(macro.id);
+                            }}
+                            disabled={isDeleting}
+                          />
+                           setMacroToDelete(null)}
+                          />
+                          
                       
-                    
-                  )
-                )}
-              
+                    ) : (
+                      <>
+                        
 handleEditMacro(macro)}
+                        />
+                         handleDuplicateMacro(macro)}
+                        />
+                         setMacroToDelete(macro.id)}
+                          className="text-red-500 dark:text-red-400"
+                        />
+                      >
+                    )}
+                     
+                
+              )
             )}
           
         )}