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 35c86fd..9896c5c 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}
-
+
);
};
return (
-
-
- {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"
/>
-
-
-
- {
setNewMacro(prev => ({ ...prev, description: e.target.value }));
if (errors.description) {
@@ -1017,11 +994,9 @@ export default function SettingsMacrosRoute() {
setErrors(newErrors);
}
}}
- placeholder="Description (optional)"
/>
-
-
+
@@ -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 ? (
-
-
-
- {
- setEditingMacro({ ...editingMacro, name: e.target.value });
- if (errors.name) {
- const newErrors = { ...errors };
- delete newErrors.name;
- setErrors(newErrors);
- }
- }}
- placeholder="Macro Name"
- />
-
-
-
- {
- setEditingMacro({ ...editingMacro, description: e.target.value });
- if (errors.description) {
- const newErrors = { ...errors };
- delete newErrors.description;
- setErrors(newErrors);
- }
- }}
- placeholder="Description (optional)"
- />
-
-
+
+ {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({
+ ...editingMacro,
+ steps: [
+ ...editingMacro.steps,
+ { keys: [], modifiers: [], delay: DEFAULT_DELAY }
+ ],
+ });
+ clearErrors();
+ }}
+ disabled={editingMacro.steps.length >= MAX_STEPS_PER_MACRO}
+ />
+
+
+
+
+
+ {
+ 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"
+ />
+ >
+ )}
+
+
+ )
)}
)}