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 && ( - + 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} - + LeadingIcon={LuX} + /> ))}
@@ -313,17 +308,13 @@ function MacroStepCard({
- + 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 && ( +
+
-
- )} + )} -
- -
- {!showAddMacro && ( -
-
-
+ {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} - /> - ))} - -
- +
+
+ {(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? - -
+ +
+ {showClearConfirm ? ( +
+ + Cancel changes? + +
+ ) : ( +
+
- ) : ( -
-
- )} -
+ } + }} + /> +
+ )}
)} + {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); + } + }} + /> +
+
+ +
+
+ + + {editingMacro.steps.length}/{MAX_STEPS_PER_MACRO} steps + +
+ {errors.steps && errors.steps[0]?.keys && ( +
+
- -
-
- - - {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} + /> + ))} +
+
+ +
+ -
-
- -
-
-
-
+ setEditingMacro({ + ...editingMacro, + steps: [ + ...editingMacro.steps, + { keys: [], modifiers: [], delay: DEFAULT_DELAY } + ], + }); + clearErrors(); + }} + disabled={editingMacro.steps.length >= MAX_STEPS_PER_MACRO} + /> +
+ +
+
+
- ) : ( -
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? -
- ) : ( - <> - - - - + ); + })} + {macro.steps.length > 3 && ( + + + {macro.steps.length - 3} more steps + )} + +

+
+ +
+ {macroToDelete === macro.id ? ( +
+ + Delete macro? + +
+
-
- ) - )} -
+ ) : ( + <> +
+
+ ) )}
)}